2015-12-03 395 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