在使用一次性对象时,我遇到了尝试并行处理多个任务(或运行时感觉合适)的问题。在下面的代码片段中,每个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();
}
}
}
有人可以解释为什么第一个例子正在处理'早期',并举例说明这种情况的最佳代码模式。
答案 0 :(得分:7)
将await
视为暂停当前方法会有所帮助,即使它不会阻止线程。
在第一个示例中,当您执行foreach
循环时,每次创建Processor
时,都要启动操作(将操作的Task
保存在列表中),然后处置Processor
。完成foreach
循环后,您(异步)等待所有操作完成。
在第二个示例中,当您执行foreach
循环时,每次创建Processor
时,启动一个操作,(异步)等待它完成,然后处理{{ 1}}。
要解决此问题,您应该编写一个帮助方法,如下所示:
Processor
您的帮助程序方法定义了一个更高级别的操作,它将在适当的时候处理自己的private static async Task ProcessData(int data)
{
using (var processor = new Processor(d))
{
processor.Completed += (sender, e) => { /* Do something else */ };
await processor.ProcessAsync();
}
}
资源。然后你可以同时开始所有的工作:
Processor
答案 1 :(得分:2)
尽管Stephen Cleary的答案很优雅,在这种情况下可能是更好的选择,但我认为值得关注你在相似场景中可能有用的Reactive Extensions(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引用。
我不认为我会在这种情况下使用它,但我想我会尽量避免到达需要代码看起来像这样的地方。同时使用Task
表示您的Processor
正在完成的工作,还有一个事件要发出信号...某事情?......它是完整的......?那么Task
可以为你做到这一点,为什么两者兼而有之呢?而事件相对凌乱 - 我总是发现Rx IObservable<T>
或直接使用Task
是比依赖事件更好的解决方案。