如何等待接受CancellationToken的多个任务?

时间:2013-11-08 16:08:54

标签: .net async-await cancellation

我有使用异步方法从多个源加载数据的代码(如下所示)。

public class ThEnvironment
{
    public async Task LoadLookupsAsync(CancellationToken cancellationToken) { ... }
    public async Task LoadFoldersAsync(CancellationToken cancellationToken) { ... }
}

在我的程序的其他地方,我想加载多个环境。

ThEnvironment[] workingEnvironments = ThEnvironment.Manager.GetWorkingEnvironments();
foreach ( ThEnvironment environment in workingEnvironments )
{
    await environment.LoadLookupsAsync(CancellationToken.None);
    await environment.LoadFoldersAsync(CancellationToken.None);
}

我的问题有两个:

  1. 对于任何单一环境,我如何同时运行environment.Load ... Async()方法并仍然可以传递CancellationToken?
  2. 如何对所有环境并行执行此操作?
  3. 底线:我想并行运行所有环境的所有Load..Async()方法,并且仍然可以传递CancellationToken。


    更新

    感谢大家的快速回答。我已将代码缩减为以下内容:

    Refresh();
    
    var tasks = ThEnvironment.Manager.GetWorkingEnvironments()
        .SelectMany(e => new Task[] { e.LoadLookupsAsync(CancellationToken.None), e.LoadFoldersAsync(CancellationToken.None) });
    
    await Task.WhenAll(tasks.ToArray());
    

    我不太关心捕获所有异常,因为这不是生产应用程序。并且顶部的Refresh()处理了表单的初始绘制问题。

    各种各样的好东西。

2 个答案:

答案 0 :(得分:2)

你可以简单地启动两个Task,然后等待它们两个完成:

Task lookupsTask = environment.LoadLookupsAsync(cancellationToken);
Task foldersTask = environment.LoadFoldersAsync(cancellationToken);

await lookupsTask;
await foldersTask;

相同代码的更高效版本是使用Task.WhenAll()

await Task.WhenAll(lookupsTask, foldersTask);

如果你想为Task的集合执行此操作,而不只是其中两个,你可以用List<Task>填充所有Task,然后填充{{1} }。

这种方法的一个问题是,如果发生多个异常,那么你只会获得第一个异常。如果这对您来说是个问题,您可以使用280Z28建议的内容。

答案 1 :(得分:1)

使用相同的取消令牌启动两个操作,然后等待它们都完成。

Task lookupsTask = environment.LoadLookupsAsync(cancellationToken);
Task foldersTask = environment.LoadFoldersAsync(cancellationToken);

await Task.Factory.ContinueWhenAll(
    new[] { lookupsTask, foldersTask },
    TaskExtrasExtensions.PropagateExceptions,
    TaskContinuationOptions.ExecuteSynchronously);

这使用Parallel Extensions Extras示例代码中的PropagateExceptions扩展方法来确保加载任务中的异常信息不会丢失:

/// <summary>Propagates any exceptions that occurred on the specified tasks.</summary>
/// <param name="tasks">The Task instances whose exceptions are to be propagated.</param>
public static void PropagateExceptions(this Task [] tasks)
{
    if (tasks == null) throw new ArgumentNullException("tasks");
    if (tasks.Any(t => t == null)) throw new ArgumentException("tasks");
    if (tasks.Any(t => !t.IsCompleted)) throw new InvalidOperationException("A task has not completed.");
    Task.WaitAll(tasks);
}