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:
- Is there a .NET built-in solution I'm missing?
- 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) - 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.