At the interview they asked what would happen in this situation if both Task
throw an exception and how, in this case, should they be processed?
Task task1 = Method1Async(); Task task2 = Method2Async(); await task1; await task2;
At the interview they asked what would happen in this situation if both Task
throw an exception and how, in this case, should they be processed?
Task task1 = Method1Async(); Task task2 = Method2Async(); await task1; await task2;
First a bit of theory. As you know, async/await
is essentially just syntactic sugar, behind the scenes, the compiler deploys asynchronous code into a bit more complicated. What happens when an exception occurs in a method marked with the async
modifier? It is not immediately thrown up the stack (because by that time we can already be in a completely different place), but is placed inside the task (see the Exception
property). The method itself ends normally. The exception (original or wrapped in AggregateException
) will be thrown in several cases:
await
or the Wait()
method).Result
property, applicable only for Task<T>
).ThrowUnobservedTaskExceptions
setting.Based on this, the answer to the question is somewhat “branchist”:
Option 1. The code works under .NET under 4.5, or the ThrowUnobservedTaskExceptions
setting is ThrowUnobservedTaskExceptions
.
In this case, the exception for the first task will be thrown, the exception of the second task will remain unobserved. If somewhere above the stack, the exception from the first task was processed, and the application continued to work, then it will fall too late, when the finalizer of the second task throws an exception.
Option 2. The code works under .NET 4.5 and higher and the ThrowUnobservedTaskExceptions
setting ThrowUnobservedTaskExceptions
disabled.
In this case, the exception for the first task will still be thrown, the exception of the second task will remain unobserved (you can only see it by subscribing to the TaskScheduler.UnobservedTaskException
event), but the process will not fall.
There are several options for exception handling:
await
into a separate try/catch
. The advantage of this option is that you will receive and handle both exceptions.await Task.WhenAll(task1, task2)
. The disadvantage of this option is that you only receive information about the first exception.Task.WaitAll(task1, task2)
. The advantage of this option is that you get an AggregateException
that will contain both exceptions. The disadvantage of this option is that it blocks the current thread.The question is taken from here . The short answer is that the process will drop to 4.5, and after that it will not.
ThrowUnobservedTaskExceptions
setting is not enabled. - andreychaA small addition to the answer @andreycha (the first variant of processing): if you want to get all the exceptions at AggregateException
, but do not use the blocking Task.WaitAll
, you can use this bike:
static async Task WhenAllWithAllExceptions(IEnumerable<Task> tasks) { var materialTasks = tasks.ToList(); // пробегаем список сейчас List<Exception> exceptions = null; bool hasExceptions = false; foreach (var task in materialTasks) { try { await task.ConfigureAwait(false); } catch (Exception ex) { if (exceptions == null) exceptions = new List<Exception>(materialTasks.Count); exceptions.Add(ex); hasExceptions = true; } } if (hasExceptions) throw new AggregateException(exceptions); } static async Task<T[]> WhenAllWithAllExceptions<T>(IEnumerable<Task<T>> tasks) { var materialTasks = tasks.ToList(); // пробегаем список сейчас T[] results = null; List<Exception> exceptions = null; bool hasExceptions = false; int taskIndex = -1; foreach (var task in materialTasks) { taskIndex++; try { T t = await task.ConfigureAwait(false); if (!hasExceptions) { if (results == null) results = new T[materialTasks.Count]; results[taskIndex] = t; } } catch (Exception ex) { if (exceptions == null) exceptions = new List<Exception>(materialTasks.Count); exceptions.Add(ex); hasExceptions = true; } } if (hasExceptions) throw new AggregateException(exceptions); return results; }
Update: following the advice of @Pavel Mayorov, added ConfigureAwait
.
hasExceptions
flag hasExceptions
optional - they can be the fact of initialization / presence of exceptions in the exceptions
list. And the method can be done extension-method. - andreychahasExceptions
, it's formally superfluous, but it seemed to me that it adds readability (which in my opinion is more important than anything else). - VladDConfigureAwait(false)
to place. - Pavel Mayorov 4:27 pmSource: https://ru.stackoverflow.com/questions/545915/
All Articles
await task1
will end with an exception and the code will not reachawait task2
- VladDTask.WhenAll()
throw only one exception. If you need information on all exceptions, this is not an option. - andreycha