Reputation: 13
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:
Bottom line: I'd like to run all Load..Async() methods for all environments in parallel and still be able to pass the CancellationToken.
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.
Upvotes: 1
Views: 2007
Reputation: 244757
You can simply start both Task
s 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 Task
s, not just two of them, you can fill a List<Task>
with all the Task
s 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.
Upvotes: 2
Reputation: 99859
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);
}
Upvotes: 1