Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

await Task.WhenAll(tasks) Exception Handling, log all exceptions from the tasks

I am trying to figure out how to report all exceptions thrown by a list of tasks from the code below.

The basic idea of this code snippet is: The user sends a request to the handler, the handler creates the messages tasks and send them to a class that send them to the external system. I included the methods involved below.

I must have something off because I was debugging the exception handler and the tasks Exception are always null because it seems their status is Waiting for Activiation unless I stay in the breakpoint long enough.

// Handle the user request
public async void Handle(WriteScanToSys settings)
{
  _successfulScanIds = new List<int>();

  // create the messages as tasks
  var tasks = _factory.CreateMessage(settings).Select(msg => SendScans(msg));

  try
  {
    // wait for all of them to complete
    await Task.WhenAll(tasks); // I had ConfigureAwait(false) here, but took it off
  }
  catch (Exception)
  {
    foreach (var task in tasks.Where(t => t.Exception != null))
    {
      // ELMAH
      var errorLog = ErrorLog.GetDefault(null);
      errorLog.Log(new Error(task.Exception));
    }
  }

  // save to repository
}

// the task to perform
private async Task<IDictionary<string, object>> SendScans(IDictionary<string, object> data)
{
  object sysMsg = null;
  var response = await _msgCenter.SendMessage(data);
  response.TryGetValue("SystemMessage", out sysMsg);
  _successfulScanIds.Add(Convert.ToInt32(data["Id"]));
  return response;
}

// the communication with the external system (The message center class)
private async Task<IDictionary<string, object>> SendMessage(IDictionary<string, object> msg)
{
  var response = new Dictionary<string, object>();

  var response = await _client.sendAsync(
                          new BodyOfRequest(
                              // Compose Object
                          ));

  if (response.ScreenMessage != "SUCCESSFUL")
    throw new CustomException("The transaction for job " + job + " failed with msg: " + body.ScreenMessage);

  response.Add("SystemMessage", body.ScreenMessage);

  return response;
}
like image 923
jmzagorski Avatar asked Mar 02 '15 16:03

jmzagorski


People also ask

Does task WhenAll throw exception?

WhenAll(tasklist)", it will throw an exception if any of the tasks are faulted. Since we have 2 faulted tasks here, that's exactly what happens.

How do you handle exceptions thrown by tasks?

Exceptions are propagated when you use one of the static or instance Task. Wait methods, and you handle them by enclosing the call in a try / catch statement. If a task is the parent of attached child tasks, or if you are waiting on multiple tasks, multiple exceptions could be thrown.

What is the use of task WhenAll?

WhenAll(Task[]) Creates a task that will complete when all of the Task objects in an array have completed.

Does task WhenAll start the tasks?

WhenAll creates a task that will complete when all of the supplied tasks have been completed. It's pretty straightforward what this method does, it simply receives a list of Tasks and returns a Task when all of the received Tasks completes.


2 Answers

You've fallen foul of lazy evaluation - the result of Select will create a new set of tasks each time you iterate over it. You can fix this just by calling ToList():

var tasks = _factory.CreateMessage(settings)
                    .Select(msg => SendScans(msg))
                    .ToList();

That way the set of tasks that you're awaiting will be the same set of tasks checked with your foreach loop.

like image 107
Jon Skeet Avatar answered Nov 09 '22 23:11

Jon Skeet


Instead of iterating over all tasks, you can get the Exceptions (if any) from the Task.WhenAll-Task:

var taskResult = Task.WhenAll(tasks);
try
{
    await taskResult;
}
catch (Exception e)
{
    if (taskResult.IsCanceled)
    {
        // Cancellation is most likely due to a shared cancellation token. Handle as needed, possibly check if ((TaskCanceledException)e).CancellationToken == token etc.       
    }
    else if (taskResult.IsFaulted)
    {
        // use taskResult.Exception which is an AggregateException - which you can iterate over (it's a tree! .Flatten() might help)
        // caught exception is only the first observed exception
    }
    else
    {
        // Well, this should not really happen because it would mean: Exception thrown, not faulted nor cancelled but completed
    }
}
like image 27
urbanhusky Avatar answered Nov 09 '22 23:11

urbanhusky