使用Await Async WhenAll与一次性对象

时间:2013-07-22 20:42:49

标签: c# design-patterns async-await idisposable

在使用一次性对象时,我遇到了尝试并行处理多个任务(或运行时感觉合适)的问题。在下面的代码片段中,每个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();
        }
    }
}

有人可以解释为什么第一个例子正在处理'早期',并举例说明这种情况的最佳代码模式。

2 个答案:

答案 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是比依赖事件更好的解决方案。