我需要在控制台应用程序中运行多个异步任务,并在进一步处理之前等待它们全部完成。

有很多文章,但我似乎越读越困惑。我已经阅读并理解了Task库的基本原理,但我显然在某个地方遗漏了一个链接。

我知道可以将任务串联起来,这样它们就可以在另一个任务完成后开始(这几乎是我读过的所有文章的场景),但我希望所有任务同时运行,并且我想知道它们都完成后的情况。

对于这样的场景,最简单的实现是什么?


当前回答

您希望将任务链接起来,还是可以以并行的方式调用它们?

的链接 就像这样

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

不要忘记检查每个ContinueWith中的前一个Task实例,因为它可能有错误。

对于平行方式 我遇到的最简单的方法是:平行。调用 除此之外还有任务。WaitAll或者你甚至可以使用WaitHandles来做一个倒数到零的动作(等待,有一个新的类:CountdownEvent),或者…

其他回答

还有另一个答案……但我通常会遇到这样的情况,当我需要同时加载数据并将其放入变量中,比如:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

我所见过的最佳选择是以下扩展方法:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

这样叫它:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

或者使用async lambda:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

如果你正在使用async/await模式,你可以像这样并行运行几个任务:

public async Task DoSeveralThings()
{
    // Start all the tasks
    Task first = DoFirstThingAsync();
    Task second = DoSecondThingAsync();

    // Then wait for them to complete
    var firstResult = await first;
    var secondResult = await second;
}

两个答案都没有提到可等待任务。WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

任务之间的主要区别。WaitAll和Task。WhenAll表示前者将阻塞(类似于在单个任务上使用Wait),而后者不会阻塞,可以等待,将控制权交还给调用方,直到所有任务完成。

更重要的是,异常处理不同:

的任务。WaitAll:

至少一个Task实例被取消了,或者在执行至少一个Task实例期间抛出了异常。如果一个任务被取消了,AggregateException的InnerExceptions集合中包含一个OperationCanceledException。

的任务。WhenAll:

If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state. If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state. If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion state before it's returned to the caller.

你可以使用WhenAll,它将返回一个可等待的任务,或者WaitAll,它没有返回类型,将阻止进一步的代码执行,类似于线程。休眠,直到所有任务都完成、取消或出错。

WhenAll WaitAll
Any of the supplied tasks completes in a faulted state A task with the faulted state will be returned. The exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. An AggregateException will be thrown.
None of the supplied tasks faulted but at least one of them was canceled The returned task will end in the TaskStatus.Canceled state An AggregateException will be thrown which contains an OperationCanceledException in its InnerExceptions collection
An empty list was given An ArgumentException will be thrown The returned task will immediately transition to a TaskStatus.RanToCompletion State before it's returned to the caller.
Doesn't block the current thread Blocks the current thread

例子

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

如果你想以特定的顺序运行任务,你可以从这个答案中得到灵感。