Today my colleagues and I discussed how to handle exceptions in C# 5.0 async
methods correctly, and we wondered if awaiting multiple tasks at once also observes the exceptions that do not get unwrapped by the runtime.
Consider the following code snippet:
async Task ExceptionMethodAsync() { await Task.Yield(); throw new Exception(); } async Task CallingMethod() { try { var a = ExceptionMethodAsync(); var b = ExceptionMethodAsync(); await Task.WhenAll(a, b); } catch(Exception ex) { // Catches the "first" exception thrown (whatever "first" means) } }
What happens to the second task now? Both will be in a faulted state, but is the second task's exception now observed or unobserved?
await hides all this complexity from you, and it allows you to await the same task in ten different places (very useful for e.g. asynchronous lazy initialization). can I be assured that the method pointed by task wont be executed twice even if the task is running or ran already ? @BilalFazlani Yes, you can.
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.
When awaiting a faulted task (one that has an exception set), await will rethrow the stored exception. If the stored exception is an AggregateException it will rethrow the first and discard the rest.
WhenAll() method in . NET Core. This will upload the first file, then the next file. There is no parallelism here, as the “async Task” does not automatically make something run in in parallel.
Task.WhenAll
returns a task and like all tasks the Exception
property holds an AggregateException
that combines all exceptions.
When you await
such a task only the first exception will actually be thrown.
... Whether because of child tasks that fault, or because of combinators like Task.WhenAlll, a single task may represent multiple operations, and more than one of those may fault. In such a case, and with the goal of not losing exception information (which can be important for post-mortem debugging), we want to be able to represent multiple exceptions, and thus for the wrapper type we chose AggregateException.
... Given that, and again having the choice of always throwing the first or always throwing an aggregate, for “await” we opt to always throw the first
from Task Exception Handling in .NET 4.5
It's up to you to choose if you want to handle just the first using await task;
(true in most cases) or handle all using task.Exception
(as in my example below), but in both cases a
and b
would not raise an UnobservedTaskException
.
var task = Task.WhenAll(a, b); try { await task; } catch { Trace.WriteLine(string.Join(", ", task.Exception.Flatten().InnerExceptions.Select(e => e.Message))); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With