对一次性对象使用Await Async WhenAll

本文关键字:Await Async WhenAll 一次性 对象 | 更新日期: 2023-09-27 18:24:23

当使用一次性对象时,我在尝试并行处理多个任务时遇到了问题(或者运行时感觉合适)。在下面的代码片段中,每个Processor对象在完成所需的工作之前都会立即被处理。

async public Task ProcessData(IEnumerable<int> data)
{
    var tasks = new List<Task>();
    foreach (var d in data)
    {
        using (var processor = new Processor(d))
        {
            processor.Completed += (sender, e) => { // Do something else };
            tasks.Add(processor.ProcessAsync());
        }
    }
    await Task.WhenAll(tasks);
}

按照以下方式重新编写代码会导致每个处理器执行其处理,然后被处理,但这并不是运行多个不相互依赖的任务的最有效方式。

async public Task ProcessData(IEnumerable<int> data)
{
    foreach (var d in data)
    {
        using (var processor = new Processor(d))
        {
            processor.Completed += (sender, e) => { // Do something else };
            await processor.ProcessAsync();
        }
    }
}

有人能解释一下为什么第一个例子是"早期"处理,并给出一个针对这种情况的最佳代码模式的例子吗。

对一次性对象使用Await Async WhenAll

await视为暂停当前方法会有所帮助,即使它不会阻塞线程

在第一个示例中,当执行foreach循环时,每次创建Processor时,启动一个操作(将操作的Task保存在列表中),然后处置Processorforeach循环完成后,您(异步)等待所有操作完成。

在第二个示例中,当执行foreach循环时,每次创建Processor时,启动一个操作,(异步)等待它完成,然后处置Processor

要解决这个问题,您应该编写一个助手方法,例如:

private static async Task ProcessData(int data)
{
  using (var processor = new Processor(d))
  {
    processor.Completed += (sender, e) => { /* Do something else */ };
    await processor.ProcessAsync();
  }
}

您的helper方法定义了一个更高级别的操作,该操作将负责在适当的时候处理自己的Processor资源。然后你可以同时开始你的所有工作:

public async Task ProcessData(IEnumerable<int> data)
{
  ...
  await Task.WhenAll(data.Select(d => ProcessData(d)));
  ...
}

尽管Stephen Cleary的答案很优雅,在这种情况下可能是更好的选择,但我认为值得注意的是,作为反应式扩展(Rx)的一部分,它在类似场景中可能很有用。它提供了一组与IDisposable相关的助手,使您能够做到这一点:

public async Task ProcessData(IEnumerable<int> data)
{
    var tasks = new List<Task>();
    using (var disp = new CompositeDisposable())
    {
        foreach (var d in data)
        {
            var processor = new Processor(d);
            disp.Add(processor);
            processor.Completed += (sender, e) =>
                {
                    // Do something else
                };
            tasks.Add(processor.ProcessAsync());
        }
        await Task.WhenAll(tasks);
    }
}

要获得CompositeDisposable,您需要一个对Rx-Main的NuGet引用。

我不认为我会在这种情况下使用它,但我想我会尽量避免到达一个我需要这样代码的地方。既有代表Processor正在完成的工作的Task,也有表示。。。某物它是完整的。。?Task可以为你做到这一点,为什么两者都有呢?事件是相对混乱的——我总是发现Rx IObservable<T>或直接使用Task是比依赖事件更好的解决方案。