异步编程基础——等待一组任务完成
问题
手头有一些任务,需要等待他们去全部完成
解决方案
为了解决这个问题,框架提供了Task.WhenAll方法。这个方法接收若干个任务并在这些任务完成后返回一个已完成的任务
Task task1=Task.Delay(TimeSpan.FromSecond(1));
Task task2=Task.Delay(TimeSpan.FromSecond(1));
Task task3=Task.Delay(TimeSpan.FromSecond(1));
await Task.WhenAll(task1,task2,task3);
如果所有任务有这相同的结果类型,并且均已成功完成,那么Task.WhenAll会返回一个数组,其中包括所有任务结果:
Task<int> task1=Task.FromResult(3);
Task<int> task2=Task.FromResult(5);
Task<int> task3=Task.FromResult(7);
int[] results=await Task.WhenAll(task1,task2,task3);
//结果包含{3,5,7}
Task.WhenAll有一个重载,会接收IEnumerable中的任务,但是最好不要使用这个重载。一旦把异步代码与LINQ混合在一起,然后将序列显式地具象化(比如对序列求值,创建一个新集合),代码就会变得更为清晰。
讨论
如有任何任务抛出异常,Task.WhenAll都会将其返回的任务与该异常一同当做错误处理。如果多个任务抛出异常,那么所有这些异常都会被防止在由Task.WhenAll返回的Task中。但是,当该任务尚在等待中时,便只会抛出其中的一个异常。如果检查Task.WhenAll返回的Task的Exception属性,则可获得每一个特定的异常
async Task ThrowNotImplementedExceptionAsync()
{
throw new NotImplementedException();
}
async Task ThrowInvalidOperationExceptionAsync()
{
throw new InvalidOperationException();
}
async Task ObserveOneExceptionAsync()
{
var task1=ThrowNotImplementedExceptionAsync();
var task2=ThrowInvalidOperationExceptionAsync();
try
{
await Task.WhenAll(task1,task2);
}
cath(Exception ex)
{
//ex不是NotImplementedException就是InvalidOperationException
...
}
}
async Task ObserveAllExceptionsAsync()
{
var task1=ThrowNotImplementedExceptionAsync();
var task2=ThrowInvalidOperationExceptionAsync();
Task allTasks=Task.WhenAll(task1,task2);
try
{
await allTasks;
}
catch
{
AggregateException allExceptions=allTasks.Exception;
...
}
}
在大多数情况下,使用Task.WhenAll无需观察所有异常,通常只需要响应第一个(而非所有)抛出的异常。
注意:在上面例子中,ThrowNotImplementedExceptionAsync方法和ThrowInvalidOperationExceptionAsync方法并没有直接抛出各自的异常。因为他们都使用了Async关键字,所以当他们的异常被捕获后,会被放置在一个正常返回的任务中,对返回等待类型的方法而言,这种情况是完全正常的。