The original method is the inefficient foreach
loop that awaits every job (which is an I/O bound network call):
foreach (var job in Jobs)
{
try
{
await DoJobAsync(job); //Pass job to external vendor API
job.Succeeded = true;
}
catch (Exception)
{
//do nothing, let loop continue
}
}
Now to improve performance, I want to use Task.WhenAll
to process all jobs in a non-blocking manner.
However, we need to make sure that each job
object only has that Succeeded
property set to true
if the DoJobAsync
task does not throw exception.
If we do this:
await Task.WhenAll(Jobs.Select(j =>
{
var task = DoJobAsync(j);
j.Succeeded = true;
return task;
}));
My understanding is that if any job task ends up throwing exception, that property will still get toggled to true
because each individual task is not awaited as it is created, making the code flow go right past it.
I know I can capture the Task
returned by Task.WhenAll
to have access to a list of all exceptions thrown, but I can't figure out a way to use them to trace back to the job
that threw the exception.
How do I work around this issue?
CodePudding user response:
Return the result true/false from the method DoJobAsync().
This way you can return the result directly.
await Task.WhenAll(Jobs.Select(j =>
{
return DoJobAsync(j);
}));
CodePudding user response:
You could use an async delegate as the selector
of the Select
operator:
await Task.WhenAll(Jobs.Select(async job =>
{
await DoJobAsync(job);
job.Succeeded = true;
}));
This way each job will be projected to a Task
that will not be the original DoJobAsync(job)
task, but instead a wrapper task that encapsulates the logic for updating the Succeeded
property. This property will be updated immediately after the DoJobAsync(job)
task completes successfully.
It is possible that multiple Job
objects might have their Succeeded
property updated in parallel. It depends on whether a SynchronizationContext
is installed on the current thread.