Question

I have code that loads data from multiple sources using async methods (shown below).

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

Elsewhere in my program, I want to load multiple environments.

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

My question is twofold:

  1. For any single environment, how would I run both environment.Load...Async() methods in parallel and still be able to pass the CancellationToken?
  2. How would I do this for all environments in parallel?

Bottom line: I'd like to run all Load..Async() methods for all environments in parallel and still be able to pass the CancellationToken.


update

Thanks to everyone for the very quick answers. I've reduced the code down to the following:

Refresh();

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

await Task.WhenAll(tasks.ToArray());

I'm not too concerned about capturing all exceptions since this is not a production application. And the Refresh() at the top took care of the form's initial painting issue.

Great stuff all around.

Was it helpful?

Solution

You can simply start both Tasks and then wait for both of them to complete:

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

await lookupsTask;
await foldersTask;

A more efficient version of the same code would be to use Task.WhenAll():

await Task.WhenAll(lookupsTask, foldersTask);

If you want to do this for a collection of Tasks, not just two of them, you can fill a List<Task> with all the Tasks and then await Task.WhenAll(tasksList).

One issue with this approach is that if more than one exception happens, you're only going to get the first one. If that's a problem for you, you can use something like what 280Z28 suggested.

OTHER TIPS

Start both actions with the same cancellation token, and then wait for both of them to complete.

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

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

This uses the PropagateExceptions extension method from the Parallel Extensions Extras sample code to ensure that exception information from the load tasks is not lost:

/// <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);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top