• 如果正在 await 一批任务,希望在每个任务完成时对它做一些处理。另外,希望在任务一完成就立即进行处理,而不需要等待其他任务。

按顺序 await 每个任务

举个例子,下面的代码启动了 3 个延时任务,然后对每一个进行 await。

  1. static async Task<int> DelayAndReturnAsync(int val)
  2. {
  3. await Task.Delay(TimeSpan.FromSeconds(val));
  4. return val;
  5. }
  6. // 当前,此方法输出 2 3 1
  7. // 我们希望它输出 1 2 3
  8. static async Task ProcessTasksAsync()
  9. {
  10. // 创建任务队列。
  11. Task<int> taskA = DelayAndReturnAsync(2);
  12. Task<int> taskB = DelayAndReturnAsync(3);
  13. Task<int> taskC = DelayAndReturnAsync(1);
  14. var tasks = new[] { taskA, taskB, taskC };
  15. // 按顺序 await 每个任务。
  16. foreach (var task in tasks)
  17. {
  18. var result = await task;
  19. Trace.WriteLine(result);
  20. }
  21. }

按任务完成的次序进行处理,不必等待其他任务

  • 虽然列表中的第二个任务是首先完成的,当前这段代码仍按列表的顺序对任务进行 await。
  • 如果我们希望按任务完成的次序进行处理(例如 Trace.WriteLine),不必等待其他任务,可以考虑下面的方案:

  • 方案一

  1. static async Task AwaitAndProcessAsync(Task<int> task)
  2. {
  3. var result = await task;
  4. Trace.WriteLine(result);
  5. }
  6. // 现在,这个方法输出 1 2 3
  7. static async Task ProcessTasksAsync()
  8. {
  9. // 创建任务队列。
  10. Task<int> taskA = DelayAndReturnAsync(2);
  11. Task<int> taskB = DelayAndReturnAsync(3);
  12. Task<int> taskC = DelayAndReturnAsync(1);
  13. var tasks = new[] { taskA, taskB, taskC };
  14. var processingTasks = (from t in tasks
  15. select AwaitAndProcessAsync(t)).ToArray();
  16. // 等待全部处理过程的完成。
  17. await Task.WhenAll(processingTasks);
  18. }
  • 方案二:上面的代码也可以这么写:
  1. static async Task ProcessTasksAsync()
  2. {
  3. Task<int> taskA = DelayAndReturnAsync(2);
  4. Task<int> taskB = DelayAndReturnAsync(3);
  5. Task<int> taskC = DelayAndReturnAsync(1);
  6. var tasks = new[] { taskA, taskB, taskC };
  7. var processingTasks = tasks.Select(async t =>
  8. {
  9. var result = await t;
  10. Trace.WriteLine(result);
  11. }).ToArray();
  12. await Task.WhenAll(processingTasks);
  13. }
  • 重构后的代码是解决本问题较清晰、可移植性较好的方法。不过它与原始代码有一个细微的区别。重构后的代码并发地执行处理过程,而原始代码是一个接着一个地处理。