异步等待WhenAll不等待

本文关键字:等待 WhenAll 异步 | 更新日期: 2023-09-27 18:25:34

我正在VS2013.NET FW 4.5.1中开发WinForms应用程序。这是我的精简代码,带有关于结构的内联注释:

// 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);
}

正如您所看到的,我构建了进度对象,然后我就有了列表列表。顶层列表中的每个项目都应该以序列化的方式执行(一个接一个)。每个项目的列表元素,都应该以任务的形式同时执行。到目前为止,一切都很好,所有任务都开始了,甚至WhenAll都在等待它们。或者至少我是这么认为的。我已经把日志记录放在了相关的方法中,向我展示了代码的执行情况。事实证明,当进度逻辑(在底部)正在执行时,foreach循环开始执行另一批任务,而这是不应该的。我是不是遗漏了什么?进度代码是否不阻止或等待Report方法完成执行。也许我错过了异步/等待的一些内容。使用wait,我们确保代码在方法完成之前不会继续?它不会阻塞当前线程,但也不会继续执行?在进度报告仍在进行的情况下,我的foreach循环是否有可能继续执行?

此代码位于异步方法中。它实际上是这样调用的(假设这个方法是async MyProblematicMethod()):

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

从MyProblematicMethod开始的每个方法都使用等待来等待异步方法,并且调用次数不多。

异步等待WhenAll不等待

根据Glorin的建议,IProgress.Report在触发事件处理程序后立即返回,我创建了Progress类的精确副本,它使用synchronizationContext.Send而不是Post:

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.Report将等待方法完成,然后返回。也许不是所有情况下的最佳解决方案,但在这种情况下它对我有效。

要调用它,只需使用SynchronizationContext.Current创建ProgressEx作为构造函数的参数。但是,它必须在UI线程中创建,因此会传入正确的SynchronizationContext。例如,new ProgressEx<MyDataObject>(SynchronizationContext.Current)