首页 > 软件开发 > C# > 正文
2.6 任务完成时的处理
2014-12-06 14:12:10     我来说两句      
收藏    我要投稿
问题
 
正在await 一批任务,希望在每个任务完成时对它做一些处理。另外,希望在任务一完成就立即进行处理,而不需要等待其他任务。
 
举个例子,下面的代码启动了3 个延时任务,然后对每一个进行await。

static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
// 当前,此方法输出 “2”, “3”, “1”。
// 我们希望它输出 “1”, “2”, “3”。
static async Task ProcessTasksAsync()
{
// 创建任务队列。
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
// 按顺序await 每个任务。
foreach (var task in tasks)
{
var result = await task;
Trace.WriteLine(result);
}
}
虽然列表中的第二个任务是首先完成的,当前这段代码仍按列表的顺序对任务进行await。
我们希望按任务完成的次序进行处理(例如Trace.WriteLine),不必等待其他任务。
 
解决方案
 
解决这个问题的方法有好几种。本节首先介绍一种推荐大家使用的方法,另一种则将在“讨论部分”给出。
 
最简单的方案是通过引入更高级的async 方法来await 任务,并对结果进行处理,从而重新构建代码。提取出处理过程后,代码就明显简化了。
static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
static async Task AwaitAndProcessAsync(Task<int> task)
{
var result = await task;
Trace.WriteLine(result);
}
// 现在,这个方法输出“1”, “2”, “3”。
static async Task ProcessTasksAsync()
{
// 创建任务队列。
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
var processingTasks = (from t in tasks
select AwaitAndProcessAsync(t)).ToArray();
// 等待全部处理过程的完成。
await Task.WhenAll(processingTasks);
}
上面的代码也可以这么写:

static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
// 现在,这个方法输出 “1”, “2”, “3”。
static async Task ProcessTasksAsync()
{
// 创建任务队列。
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
var processingTasks = tasks.Select(async t =>
{
var result = await t;
Trace.WriteLine(result);
}).ToArray();
// 等待全部处理过程的完成。
await Task.WhenAll(processingTasks);
}

重构后的代码是解决本问题最清晰、可移植性最好的方法。不过它与原始代码有一个细微的区别。重构后的代码并发地执行处理过程,而原始代码是一个接着一个地处理。大多数情况下这不会有什么影响,但如果不允许有这种区别,可考虑使用锁(11.2 节介绍)或者后面介绍的可选方案。
 
讨论
 
如果上面重构代码的办法不可取,我们还有可选方案。Stephen Toub 和Jon Skeet 都开发了一个扩展方法,可以让任务按顺序完成,并返回一个任务数组。Setphen Toub 的解决方案见博客文档“Parallel Programming with .NET”(https://t.cn/RhR2V6n),Jon Skeet 的解决方案见他的博客(https://t.cn/RhR2xu9)。
 
这个扩展方法也可在开源项目AsyncEx 库(https://nitoasyncex.codeplex.com)找到, 它在NuGet 包Nito.AsyncEx(https://www.nuget.org/packages/Nito.AsyncEx)中。
 
使用像OrderByCompletion 这样的扩展方法,就能让修改原代码的量降到最低。

static async Task<int> DelayAndReturnAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
 
// 现在,这个方法输出 “1”, “2”, “3”。
static async Task UseOrderByCompletionAsync()
{
// 创建任务队列。
Task<int> taskA = DelayAndReturnAsync(2);
Task<int> taskB = DelayAndReturnAsync(3);
Task<int> taskC = DelayAndReturnAsync(1);
var tasks = new[] { taskA, taskB, taskC };
 
// 等待每一个任务完成。
foreach (var task in tasks.OrderByCompletion())
{
var result = await task;
Trace.WriteLine(result);
}
}
 
参阅
 
2.4 节介绍异步地等待一系列任务完成。

点击复制链接 与好友分享!回本站首页
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:2.5 等待任意一个任务完成
下一篇:2.7 避免上下文延续
相关文章
图文推荐
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站