2015-12-03 392 views
2

我正在開發VS2013,.NET FW 4.5.1中的WinForms應用程序。這是我與有關結構內部註釋減少代碼:異步等待WhenAll不等待

// Progress object implementing IProgress<MyProgressData> 
var progressCallback = new Progress<MyProgressData>(); 

// listOfMyList is actually List<List<MyObject>>, which contains list of 
// list of MyObject's which will be executed as tasks at once. 
// For example, this would be sample structure for list of lists: 
// List1 
// MyObject1 
// MyObject2 
// MyObject3 
// List2 
// MyObject4 
// MyObject5 
// MyObject6 
// List1's and List2's objects would be executed as all tasks at once, but List1 and List2 respectively 
// would be executed one after another (because of resources usage inside TASK CODE) 
foreach (var myItem in listOfMyList) 
{ 
    var myList = myItem.ToList(); 

    // Create a list of tasks to be executed (20 by default; each taking from 30-60 seconds) 
    // Here cs is actually MyObject 
    var myTasks = myList.Select(cs => Task.Run(async() => 
    { 
    // TASK CODE (using cs as an input object and using "cancellationToken.ThrowIfCancellationRequested();" inside execution to cancel executing if requested) 
    }, cancellationToken)); 

    await Task.WhenAll(myTasks); // Wait for all tasks to finish 

    // Report progress to main form (this actually calls an event on my form) 
    await Task.Run(() => progressCallback.Report(new MyProgressData() { props }), CancellationToken.None); 
} 

正如你所看到的,我建造進度的對象,然後我列出的清單。頂層列表中的每個項目都應以序列化方式執行(一個接一個地執行)。每個項目的列表元素應該以任務的形式立即執行。 到目前爲止,所有的任務都開始了,甚至什麼時候都會等待它們。或者至少我是這麼想的。我已經把相關的方法記錄下來,向我展示代碼執行。事實證明,雖然進程邏輯(在底部)正在執行,但foreach循環開始執行另一批任務,但它不應該這樣做。 我在這裏錯過了什麼嗎?是否阻止進程代碼或等待Report方法完成執行。也許我錯過了異步/等待。等待,我們確保代碼不會繼續,直到方法完成後?它不會阻塞當前線程,但它也不會繼續執行? 它甚至有可能(因爲它的發生,它可能是),我的foreach循環繼續執行,而進度報告仍然在旅途中?

此代碼駐留在異步方法中。它實際上是所謂的像這樣(讓我們假設這種方法是async MyProblematicMethod()):

while (true) 
{ 
    var result = await MyProblematicMethod(); 
    if (result.HasToExitWhile) 
     break; 
} 

從MyProblematicMethod利用一切方法了等待,等待異步方法,而不是多次調用。

+1

無法在此處重現您的問題。在任務完成之前,執行不會傳遞任何「await」操作符。我猜你的錯誤在更高的地方。也許你的方法被多次調用? –

+0

編輯問題以顯示其調用方式。這是異步的,無處不在。有可能,我在其他地方發現了bug,幾乎在一週內找不到它。 – Jure

+0

這應該工作。發佈任務代碼。也許lambda體返回。 – usr

回答

0

基於Glorin的建議,即IProgress.Report射擊的事件處理程序後立即返回,我創建了進步類的精確副本,它使用synchronizationContext.Send,而不是帖子:

public sealed class ProgressEx<T> : IProgress<T> 
{ 
    private readonly SynchronizationContext _synchronizationContext; 
    private readonly Action<T> _handler; 
    private readonly SendOrPostCallback _invokeHandlers; 

    public event EventHandler<T> ProgressChanged; 

    public ProgressEx(SynchronizationContext syncContext) 
    { 
     // From Progress.cs 
     //_synchronizationContext = SynchronizationContext.CurrentNoFlow ?? ProgressStatics.DefaultContext; 
     _synchronizationContext = syncContext; 
     _invokeHandlers = new SendOrPostCallback(InvokeHandlers); 
    } 

    public ProgressEx(SynchronizationContext syncContext, Action<T> handler) 
     : this(syncContext) 
    { 
     if (handler == null) 
      throw new ArgumentNullException("handler"); 
     _handler = handler; 
    } 

    private void OnReport(T value) 
    { 
     // ISSUE: reference to a compiler-generated field 
     if (_handler == null && ProgressChanged == null) 
      return; 
     _synchronizationContext.Send(_invokeHandlers, (object)value); 
    } 

    void IProgress<T>.Report(T value) 
    { 
     OnReport(value); 
    } 

    private void InvokeHandlers(object state) 
    { 
     T e = (T)state; 
     Action<T> action = _handler; 
     // ISSUE: reference to a compiler-generated field 
     EventHandler<T> eventHandler = ProgressChanged; 
     if (action != null) 
      action(e); 
     if (eventHandler == null) 
      return; 
     eventHandler((object)this, e); 
    } 
} 

這意味着ProgressEx 。報告將返回前等待方法完成。也許在所有情況下都不是最好的解決方案,但在這種情況下它對我有效。

要調用它,只需使用SynchronizationContext.Current作爲構造函數的參數創建ProgressEx。但是,它必須在UI線程中創建,所以正確的SynchronizationContext被傳入。 new ProgressEx<MyDataObject>(SynchronizationContext.Current)

+1

實際上,這*打破* IProgress應該以線程安全的方式向任何想要通知的人報告進度 - 即不強制線程和偵聽器之間的任何同步。另一方面,這個類會阻止*甚至可能等待訂閱者的死鎖。爲什麼不直接調用'progressCallback.Report'而不嘗試將它包裝在任何任務中? –

+0

我已經將它包裝成一個任務,等待它完成。但是現在我已經看到我沒有做到這一點,所以我會嘗試在沒有Task.Run的情況下調用它。 – Jure