对一次性对象使用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
视为暂停当前方法会有所帮助,即使它不会阻塞线程。
在第一个示例中,当执行foreach
循环时,每次创建Processor
时,启动一个操作(将操作的Task
保存在列表中),然后处置Processor
。foreach
循环完成后,您(异步)等待所有操作完成。
在第二个示例中,当执行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
是比依赖事件更好的解决方案。