Home > Net >  Execute multiple async tasks sequentially, continuing on exception
Execute multiple async tasks sequentially, continuing on exception

Time:10-27

I'd like to execute multiple async tasks sequentially.

foreach (var msg in messages)
{
    await smtp.SendAsync(msg);
}

However, if one of the tasks fails, I'd like the rest of them to continue. And throw one exception at the end.

Think of it like .WhenAll() that executes all the tasks and throws an AggregateException at the end, if any of the tasks fails. However, I cannot find any sequential alternative.

I studied the docs and googled and stackoverflowed for a solution, and I found no built-in way to do this. I think the only way is to handle exceptions manually.

Something like this

var exceptions = new List<Exception>();
foreach (var msg in messages)
{
    try
    {
        await smtp.SendAsync(msg).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        exceptions.Add(ex);
    }
}
if (exceptions.Count > 0) 
{
    throw new AggregateException(exceptions);
}

Questions:

  1. Is there a .NET built-in solution I'm missing?
  2. If not, is this the right way? Maybe chaining multiple .ContinueWith calls and returning the resulting "chain-task" instead is considered a better practice? (thus eliding await/async and prevent unnecessary state-machine building)
  3. Am I allowed to use the .NET built-in AggregateException here or this is bad practice?

CodePudding user response:

Is there a .NET built-in solution I'm missing?

No. This is an unusual need, so there's nothing built-in. When doing sequential operations, it's far more common to want to stop on the first exception.

If not, is this the right way?

Yes. You can use AggregateException if that's the semantics you want. You should prefer await over ContinueWith.

You may also want to consider a railway programming pattern. There isn't anything like a Try/Error/Exceptional monad in the BCL but there are several libraries out there to fill that gap

CodePudding user response:

Another idea is to maintain a list of tasks instead of a list of exceptions. On each iteration you could create one SendMailAsync task, put it in the list, and await it without throwing the exception. After all tasks have been completed, you could combine them in a single Task with the Task.WhenAll method, and finally Wait this combined task to propagate all the exceptions in an AggregateException:

List<Task> tasks = new();
foreach (var msg in messages)
{
    Task task = smtp.SendMailAsync(msg);
    tasks.Add(task);
    await Task.WhenAny(task).ConfigureAwait(false);
}
Task.WhenAll(tasks).Wait();

The Task.WhenAny is used as a way to suppress the error of the awaited task. Unfortunately Microsoft hasn't accepted the proposal to support awaiting a task without throwing. The Task.WhenAny gets the job done, but not in the most efficient way possible.

  • Related