异步编程基础——在任务完成时处理它们
问题
假设有个任务集合需要等待,而在每个任务完成时,需要执行一些处理。但是,最好能在任务完成时即可进行处理,而不是等待其他任务完成。
async Task<int> DelayAndReturnAsync(int value)
{
await Task.Delay(TimeSpan.FromSeconds(value));
return value;
}
//当前,此方法打印“2” “3” “1”,理想状况是打印“1” “2” ”3“
async Task ProcessTasksAsync()
{
//创建任务序列
Task<int> taskA=DelayAndReturnAsync(2);
Task<int> taskB=DelyaAndReturnAsync(3);
Task<int> taskC=DelayAndReturnAsync(1);
Task<int>[] tasks=new[]{taskA,taskB,taskC};
//按顺序等待每个任务
foreach(Task<int> task in tasks)
{
var result=await task;
Trace.WriteLine(result);
}
当前,代码按照序列的顺序等待每个任务,即使序列中的第三个任务是最先完成的,也会如此。我们要实现的是,在每个任务完成时,可以不等待其他任务而直接进行处理,比如Trace.WriteLine.
解决方案
这个问题有若干中解决方案。最简单的解决方案是重构代码,引入高层及的async方法来处理对任务的等待及其结果,一旦把处理过程分解出来,代码就会大大简化:
async Task<int> DelayAndReturnAsync(int value)
{
await Task.Delay(TimeSpan.FromSeconds(value);
return value;
}
async Task AwaitAndProcessAsync(Task<int> task)
{
int result=await task;
Trace.WriteLine(result);
}
//现在该方法打印“1” “2” “3”
async Task ProcessTaskAsync()
{
//创建任务序列
Task<int> taskA=DelayAndReturnAsync(2);
Task<int> taskB=DelayAndReturnAsync(3);
Task<int> taskC=DelayAndReturnAsync(1);
Task<int>[] tasks=new {taskA,taskB,taskC};
IEnumerable<Task> taskQuery=from t in tasks select AwaitAndProcessAsync(t);
Task[] processingTasks=taskQuery.ToArray();
//等待所有处理工作完成
await Task.WhenAll(processingTasks);
}
这段代码也可以这样写:
async Task<int> DelayAndReturnAsync(int value)
{
await Task.Delay(TimeSpan.FromSeconds(value);
return value;
}
//现在该方法打印“1” “2” “3”
async Task ProcessTaskAsync()
{
//创建任务序列
Task<int> taskA=DelayAndReturnAsync(2);
Task<int> taskB=DelayAndReturnAsync(3);
Task<int> taskC=DelayAndReturnAsync(1);
Task<int>[] tasks=new {taskA,taskB,taskC};
IEnumerable<Task> taskQuery=tasks.Select(ansyc t=>{
var result=await t;
Trace.WriteLine(result);
}).ToArray();
//等待所有处理工作完成
await Task.WhenAll(processingTasks);
}
讨论
如果重构不是令人满意的解决方案,那么还有一个替代方案,Stephen Toub和Jon Skeet都搁置开发了扩展方法,这两个扩展方法可以返回按顺序完成的任务数组。