2017-03-10 48 views
0

我有一個利用TPL的C#WinForms(.NET 4.5.2)應用程序。該工具具有同步功能,該功能被傳遞到任務工廠X次數(具有不同的輸入參數),其中X是在開始該過程之前用戶聲明的數字。任務開始並存儲在List<Task>中。C#在任何給定時間運行X個任務<T>,同時保持WinForm響應

假設用戶輸入5,我們有這個在async按鈕單擊處理程序:

for (int i = 0; i < X; i++) 
{ 
    var progress = Progress(); // returns a new IProgress<T> 
    var task = Task<int>.Factory.StartNew(() => MyFunction(progress), TaskCreationOptions.LongRunning); 
    TaskList.Add(task); 
} 

每個progress實例更新UI。

現在,只要一個任務完成,我想要啓動一個新的。從本質上講,這個過程應該無限期地運行,任何時候都有X任務運行,除非用戶通過UI取消(我將使用取消令牌)。我嘗試使用以下方法來實現此目的:

while (TaskList.Count > 0) 
{ 
    var completed = await Task.WhenAny(TaskList.ToArray());         

    if (completed.Exception == null) 
    { 
     // report success 
    } 
    else 
    { 
     // flatten AggregateException, print out, etc 
    } 
    // update some labels/textboxes in the UI, and then: 
    TaskList.Remove(completed); 
    var task = Task<int>.Factory.StartNew(() => MyFunction(progress), TaskCreationOptions.LongRunning); 
    TaskList.Add(task); 
} 

這是陷入用戶界面。有沒有更好的方法來實現這個功能,同時保持UI的響應?

有人建議在使用TPL數據流提出的意見,但由於時間的限制和規範,替代解決方案,歡迎

更新

我不知道進展情況報告是否可能是問題?這裏是什麼樣子:

private IProgress<string> Progress() 
{ 
    return new Progress<string>(msg => 
    { 
     txtMsg.AppendText(msg); 
    }); 
} 
+1

是否有任何理由不使用專門的工具來完成此任務?您的場景正是TPL Dataflow所設計的。 –

+0

是的,我第二次基里爾 - TPL Dataflow聽起來很適合這個。 –

+0

時間限制,項目規格,並沒有意識到數據流(將有一個閱讀,並無論如何,建議,謝謝) – globetrotter

回答

3

現在,只要任務完成後,我想啓動一個新的。從本質上講,這個過程應該無限期地運行,其在任何給定的時間

這聽起來我跑X的任務,比如你想要一個無限循環你的任務:

for (int i = 0; i < X; i++) 
{ 
    var progress = Progress(); // returns a new IProgress<T> 
    var task = RunIndefinitelyAsync(progress); 
    TaskList.Add(task); 
} 

private async Task RunIndefinitelyAsync(IProgress<T> progress) 
{ 
    while (true) 
    { 
    try 
    { 
     await Task.Run(() => MyFunction(progress)); 
     // handle success 
    } 
    catch (Exception ex) 
    { 
     // handle exceptions 
    } 
    // update some labels/textboxes in the UI 
    } 
} 

然而,我懷疑「陷入用戶界面」可能在// handle success和/或// handle exceptions代碼中。如果我的懷疑是正確的,那麼儘可能多地將邏輯推入Task.Run

+0

謝謝!我已經更新了我的'//句柄成功/異常'片段,沒什麼特別。因此,'RunIndefinitelyAsync'將'等待'每個'Task.Run'調用,完成後,移動到'while'循環的下一個迭代,基本上達到相同的效果?那麼如何取消這些?用'while(SomeFlag)'替換'while(true)'會更好嗎?什麼是你在你的代碼片段中捕獲的'Exception'?我的函數中沒有try/catch塊被任務調用,我只是在'Task.WaitAny'之後檢查每個任務的'.Exception'屬性中的'AggregateException'。什麼更合適? – globetrotter

+0

您可以使用標準的'CancellationTokenSource' /'CancellationToken'方法取消這些操作,'MyFunction'通過觀察'CancellationToken'來取消。不要使用'while(SomeFlag)'來模擬取消 - 最好在循環體中使用'cancellationToken.ThrowIfCancellationRequested()'。通過檢查異常,你的代碼在邏輯上正在嘗試/捕獲; 'async'代碼只是將邏輯try/catch變爲* true * try/catch,這更合適。 –

1

據我所知,您只需要一個並行執行與定義的並行度。有很多方法來實現你想要的。我建議使用阻塞收集和並行類而不是任務。

所以,當用戶點擊按鈕時,你需要創建一個新的阻塞集合,這將是您的數據源:

BlockingCollection<IProgress> queue = new BlockingCollection<IProgress>(); 
CancellationTokenSource source = new CancellationTokenSource(); 

現在你需要一個亞軍,這將並行執行的:

Task.Factory.StartNew(() => 
    Parallel.For(0, X, i => 
    { 
     foreach (IProgress p in queue.GetConsumingEnumerable(source.Token)) 
     { 
      MyFunction(p); 
     } 
    }), source.Token); 

或者你可以選擇更正確的方式與分區。所以,你需要一個分區類:

private class BlockingPartitioner<T> : Partitioner<T> 
{ 
    private readonly BlockingCollection<T> _Collection; 
    private readonly CancellationToken _Token; 

    public BlockingPartitioner(BlockingCollection<T> collection, CancellationToken token) 
    { 
     _Collection = collection; 
     _Token = token; 
    } 

    public override IList<IEnumerator<T>> GetPartitions(int partitionCount) 
    { 
     throw new NotImplementedException(); 
    } 

    public override IEnumerable<T> GetDynamicPartitions() 
    { 
     return _Collection.GetConsumingEnumerable(_Token); 
    } 

    public override bool SupportsDynamicPartitions 
    { 
     get { return true; } 
    } 
} 

和亞軍將是這樣的:

ParallelOptions Options = new ParallelOptions(); 
Options.MaxDegreeOfParallelism = X; 

Task.Factory.StartNew(
    () => Parallel.ForEach(
     new BlockingPartitioner<IProgress>(queue, source.Token), 
     Options, 
     p => MyFunction(p))); 

所以你現在需要的是填補queue必要的數據。你可以隨時做到。

和最後的觸摸,當用戶取消操作時,你有兩個選擇:

  • 首先,你可以打破執行與source.Cancel電話,
  • 也可以優雅地通過標記收集完整的停止執行(queue.CompleteAdding)在這種情況下,跑步者將執行所有已排隊的數據並完成。

當然,您需要額外的代碼來處理異常,進度,狀態等。但主要想法在這裏。

+0

開始新的是危險的,不應該在這種情況下使用 – VMAtm

+0

@arbiter,謝謝。目前我對此解決方案沒有任何評論,因爲它利用了我沒有經歷過的東西。我會在週末解決你的問題,然後回來提問。 – globetrotter