How can I use Async with ForEach?

James Jeffery picture James Jeffery · Sep 7, 2013 · Viewed 124.1k times · Source

Is it possible to use Async when using ForEach? Below is the code I am trying:

using (DataContext db = new DataLayer.DataContext())
{
    db.Groups.ToList().ForEach(i => async {
        await GetAdminsFromGroup(i.Gid);
    });
}

I am getting the error:

The name 'Async' does not exist in the current context

The method the using statement is enclosed in is set to async.

Answer

Stephen Cleary picture Stephen Cleary · Sep 7, 2013

List<T>.ForEach doesn't play particularly well with async (neither does LINQ-to-objects, for the same reasons).

In this case, I recommend projecting each element into an asynchronous operation, and you can then (asynchronously) wait for them all to complete.

using (DataContext db = new DataLayer.DataContext())
{
    var tasks = db.Groups.ToList().Select(i => GetAdminsFromGroupAsync(i.Gid));
    var results = await Task.WhenAll(tasks);
}

The benefits of this approach over giving an async delegate to ForEach are:

  1. Error handling is more proper. Exceptions from async void cannot be caught with catch; this approach will propagate exceptions at the await Task.WhenAll line, allowing natural exception handling.
  2. You know that the tasks are complete at the end of this method, since it does an await Task.WhenAll. If you use async void, you cannot easily tell when the operations have completed.
  3. This approach has a natural syntax for retrieving the results. GetAdminsFromGroupAsync sounds like it's an operation that produces a result (the admins), and such code is more natural if such operations can return their results rather than setting a value as a side effect.