I have a hard time understanding how async/await works in various non-happy-path cases. For example, I have the following code:
class Program
{
static void Main(string[] args)
{
Do();
Console.ReadLine();
}
private static void Do()
{
TaskScheduler.UnobservedTaskException += (s, e) =>
{
Console.WriteLine($"Unobserved Exception : {e.Exception.Message}");
e.SetObserved();
};
try
{
ThrowsAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Caught in try/catch : {ex.Message}");
}
}
private static async Task ThrowsAsync()
{
Console.WriteLine("Throwing");
throw new Exception("FAILURE");
}
}
There are two things that I do not understand:
The ThrowsAsync method is async, however, it does not contain any await. I would assume that in such a case the method would execute like a "normal" synchronous method. However, the exception that it throws is never caught in the catch block.
Trying to somehow catch the exception, I added the handler for TaskScheduler.UnobservedTaskException. However, it is never executed. Why is that?
I know that the exception would be caught if I awaited ThrowsAsync. However, I'm experimenting to get a better understanding of how it works.
I'm running that code using .NET 5 and Linux-based OS.
As described for example in this blog post by Stephen Cleary - the state machine for async methods will capture exceptions from your code and place them on the returned task, i.e. method invocation will not throw, you will be able to catch exception if await the result.
As for TaskScheduler.UnobservedTaskException - check out this answer and be sure to run code in Release mode.
Related
Taken from article on async await by Stephen Cleary:
Figure 2 Exceptions from an Async Void Method Can’t Be Caught with Catch
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
... any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started...
What does that actually mean? I wrote an extended example to try and glean more info. It has the same behaviour as Figure 2:
static void Main()
{
AppDomain.CurrentDomain.UnhandledException += (sender, ex) =>
{
LogCurrentSynchronizationContext("AppDomain.CurrentDomain.UnhandledException");
LogException("AppDomain.CurrentDomain.UnhandledException", ex.ExceptionObject as Exception);
};
try
{
try
{
void ThrowExceptionVoid() => throw new Exception("ThrowExceptionVoid");
ThrowExceptionVoid();
}
catch (Exception ex)
{
LogException("AsyncMain - Catch - ThrowExceptionVoid", ex);
}
try
{
// CS1998 C# This async method lacks 'await' operators and will run synchronously.
async void ThrowExceptionAsyncVoid() => throw new Exception("ThrowExceptionAsyncVoid");
ThrowExceptionAsyncVoid();
}
// exception cannot be caught, despite the code running synchronously.
catch (Exception ex)
{
LogException("AsyncMain - Catch - ThrowExceptionAsyncVoid", ex);
}
}
catch (Exception ex)
{
LogException("Main", ex);
}
Console.ReadKey();
}
private static void LogCurrentSynchronizationContext(string prefix)
=> Debug.WriteLine($"{prefix} - " +
$"CurrentSynchronizationContext: {SynchronizationContext.Current?.GetType().Name} " +
$"- {SynchronizationContext.Current?.GetHashCode()}");
private static void LogException(string prefix, Exception ex)
=> Debug.WriteLine($"{prefix} - Exception - {ex.Message}");
Debug output:
Exception thrown: 'System.Exception' in ConsoleApp3.dll
AsyncMain - Catch - ThrowExceptionVoid - Exception - ThrowExceptionVoid
Exception thrown: 'System.Exception' in ConsoleApp3.dll
An exception of type 'System.Exception' occurred in ConsoleApp3.dll but was not handled in user code
ThrowExceptionAsyncVoid
AppDomain.CurrentDomain.UnhandledException - CurrentSynchronizationContext: -
AppDomain.CurrentDomain.UnhandledException - Exception - ThrowExceptionAsyncVoid
The thread 0x1c70 has exited with code 0 (0x0).
An unhandled exception of type 'System.Exception' occurred in System.Private.CoreLib.ni.dll
ThrowExceptionAsyncVoid
The program '[18584] dotnet.exe' has exited with code 0 (0x0).
I want more details
If there is no current synchronization context (as in my example), where is the exception raised?
What are the differences between async void (with no await) and void
The compiler warns CS1998 C# This async method lacks 'await' operators and will run synchronously.
If it runs synchronously with no await, why is it behaving differently from simply void?
Does async Task with no await also behave differently from Task?
What are the differences in compiler behaviour between async void and async Task. Is a Task object really created under-the-hood for async void as suggested here?
Edit. To be clear, this isn't a question about best practices - it is a question about compiler / runtime implementation.
If there is no current synchronization context (as in my example), where is the exception raised?
By convention, when SynchronizationContext.Current is null, that's really the same as SynchronizationContext.Current equal to an instance of new SynchronizationContext(). In other words, "no synchronization context" is the same as the "thread pool synchronization context".
So, the behavior you're seeing is that the async state machine is catching the exception and then raising it directly on a thread pool thread, where it cannot be caught with catch.
This behavior seems odd, but think about it this way: async void is intended for event handlers. So consider a UI application raising an event; if it is synchronous, then any exceptions get propagated to the UI message processing loop. The async void behavior is intended to mimic that: any exceptions (including ones after await) are re-raised on the UI message processing loop. This same logic is applied to the thread pool context; e.g., exceptions from your synchronous System.Threading.Timer callback handler will be raised directly on the thread pool, and so will exceptions from your asynchronous System.Threading.Timer callback handler.
If it runs synchronously with no await, why is it behaving differently from simply void?
The async state machine is handling the exceptions specially.
Does async Task with no await also behave differently from Task?
Absolutely. async Task has a very similar state machine - it catches any exceptions from your code and places them on the returned Task. This is one of the pitfalls in eliding async/await for non-trivial code.
What are the differences in compiler behaviour between async void and async Task.
For the compiler, the difference is just how exceptions are handled.
The proper way to think about this is that async Task is a natural and appropriate language development; async void is a weird hack that the C#/VB team adopted to enable asynchronous events without huge backwards-compatibility issues. Other async/await-enabled languages such as F#, Python, and JavaScript have no concept of async void... and thus avoid all of its pitfalls.
Is a Task object really created under-the-hood for async void as suggested here?
No.
async void - It can't be awaited and it allows you to fire or forget methods
async Task - It can be awaited, but does not return any value
async Task methodName { return default(T); } - It can be awaited, and returns a value of the type T
void - no argument will be returned
When I run this code everything works fine:
public async void InvokePlugin(MyObject xTask)
{
try
{
var hndlr = new TimeoutHandler(RunTask);
var asyncResult = hndlr.BeginInvoke(xTask, null, new object());
if (!asyncResult.AsyncWaitHandle.WaitOne(xTask.Timeout, false))
{
throw new TimeoutException("Plugin didn't complete processing in a timely manner.");
}
hndlr.EndInvoke(asyncResult);
}
catch (Exception ex)
{
//Handle Exceptions
}
}
private delegate void TimeoutHandler(MyObject xTask);
I want to update this code to use Async/Await. I tried doing it like this:
public async void InvokePlugin(MyObject xTask)
{
try
{
var runTask = Task.Run(() => { RunTask(xTask); });
if (await Task.WhenAny(runTask, Task.Delay(xTask.Timeout)) == runTask)
{
// Task completed within timeout.
// Consider that the task may have faulted or been canceled.
// We re-await the task so that any exceptions/cancellation is rethrown.
await runTask;
}
else
{
throw new TimeoutException("Plugin didn't complete processing in a timely manner.");
}
}
catch (Exception ex)
{
//Handle Exceptions
}
}
...but it's not working. Clearly I'm doing somethign wring. It does call the RunTask Method and executes the first 2 lines fine but then it just ends and I can't seem to catch the exception in either the TaskRun method or code above. All I see in the Output windows is "Program has exited with code 0 (0x0)."
If the experts out there can either point me to what I'm doing wrong or give me suggestions as to how I can catch the exception and handle it I would be very grateful.
Also if you feel I missed any important details please ask and I will update my question.
Usually I'd say if it works don't fix it but in this case I'm trying to rearchitect a bit to allow for some enhancements so here I am.
Change async void to async Task. See my article on async best practices for more information.
After you do this, consume it asynchronously:
await the task, allowing async to grow.
async and await will naturally grow upward through your code base, until they reach Main, which cannot be async.
In your Main method, call GetAwaiter().GetResult() on the "top" task.
Blocking on asynchronous code is generally not a good idea, but blocking on a single task in a Console app's Main method is an exception to that rule.
The following code does not catch my OperationCancelException which is thrown by calling ct.ThrowIfCancellationRequested.
public partial class TitleWindow : Window, IAsyncInitialization
{
public Task Initialization{get; private set;}
CancellationTokenSource cts;
public TitleWindow()
{
InitializeComponent();
cts = new CancellationTokenSource();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
cts.Cancel();
Initialization = GetCancelExceptionAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation canceled!");
}
}
public async Task GetCancelExceptionAsync(CancellationToken ct)
{
await Task.Delay(1000);
ct.ThrowIfCancellationRequested();
}
}
However if i replace my Window_Loaded method with the following (making it async and await the call of my async method), the exception gets caught.
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
cts.Cancel();
await GetCancelExceptionAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation canceled!");
}
}
Why is my first approach not working? Is the exception not properly propagated to the correct synchronization context?
I was trying to use The Asynchronous Initialization Pattern described in Stephen Clearys blog post to be able to later on await a task which was started in a constructor (and in order to make it comparable to my second example I used the (async) Window_Loaded event to await methods there right away, like suggested to me in a previous question). Then I wanted to provide an option to cancel the async method that I started in the constructor, where i am currently stuck because the exception handling does not work as I expected.
With my "non-working" code, I can catch the exception by putting await Initialization in a try-catch block somewhere, but I still get an additional unhandled exception.
How do I implement this in a way that allows me to await my async method later on (to ensure that I do not work with an inconsistent state of my object) and still being able to cancel that long-running Task (which would of course need to return/set default values)?
In your first example the exception is not caught because it does not occure before leaving the try/catch block. If you want to catch it there you need to wait/await it there exactly like you do in the second example.
If you do not await the returned task the method continues execution and leaves the try/catch block before the exception actually occures...
If you want to catch the exception "out of band" you can also register to TaskScheduler.UnobservedTaskException (this event is called if a task is throwing an exception which is nowhere caught) to get all uncaught exceptions or monitor the tasks Exception property. May also check out THIS answer.
Exeption is thrown in the task on another thread.
public async Task GetCancelExceptionAsync(CancellationToken ct)
{
try
{
await Task.Delay(1000);
ct.ThrowIfCancellationRequested();
}
catch (Exception e)
{
// your Cancleation expeption
}
}
I have a strange problem combining the async/await to make it work:
I created a small procedure, which should handle basically the try/catch of every action:
internal static void HandledAction(Action action, Info infoBar)
{
try
{
action();
}
catch (Exception ex)
{
infoBar.SetError("An Exception occured: " + ex.Message);
WriteLog(ex.StackTrace);
}
Nohing to fancy, but it's worth since changing error-handling is very easy made.
But what happens, if I'd like to get Data async in the Lambda? Lets take this simple example:
private void mnuImportData_Click(object sender, RoutedEventArgs e)
{
ActionHelper.HandledAction(async () =>
{
throw new NotImplementedException("Ups");
}, infoMain);
}
Sure, the HandledAction gets called, passes, since it gets the pointer back, and the exception gets thrown, of course not handled.
I imagine I have to create a AsyncHandledAction, and set the action async, but is there a easier way to solve this problem?
I guess many people use a central exception-handling, and there are far better solutions for this?
Thanks in advance
Matthias
Edit: I created an example, which should shpw netter I need: I basically dont want the whole Action I pass being awaitable, but one call in the Lambda is:
ActionHelper.HandledActionAsync(() =>
{
//elided
CheckFileResult rslt = await excelImport.CheckFilesAsync(tmpPath);
//elided
}, infoMain);
Of course, by doing so, I get the error:
Error 3 The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier.
The reason is: Action instead of Func. Since yours:
async () =>
{
throw new NotImplementedException("Ups");
}
in fact is:
async void Method() { }
when Func<Task> is:
async Task Method() { }
Async void will capture SynchronizationContext.Current and when exception is thrown it will be posted to SynchronizationContext by SynchronizationContext.Post() - (in windows runtime you can catch these types of exception). In case of ASP.NET/Console application SynchronizationContext.Current returns null which means that exception would be propagated to Thread Pool and I'm not sure but I think it is not possible to catch it. However when there is returned Task by asynchronous method exception could be marshaled to the caller through this returned Task. It is also worth mention that async lambda expresions will always prefer methods with Func<Task> over Action. General rule is: never use async void (async Action) unless it is "top-level method" (in example event handler).
You need an async version of HandleAction
internal static async Task HandledAction(Func<Task> action, Info infoBar)
{
try
{
await action();
}
catch (Exception ex)
{
infoBar.SetError("An Exception occured: " + ex.Message);
WriteLog(ex.StackTrace);
}
}
of course you should call the method with await
private async void mnuImportData_Click(object sender, RoutedEventArgs e)
{
await ActionHelper.HandledAction(async () =>
{
throw new NotImplementedException("Ups");
}, infoMain);
}
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.
How can we use await and at the same time throw the original AggregateException so that we do not accidentally lose error information?
Note, that it is of course possible to think of hacky solutions for this (e.g. try-catch around the await, then call Task.Wait). I really wish to find a clean solution. What is the best-practice here?
I thought of using a custom awaiter but the built-in TaskAwaiter contains lots of magic that I'm not sure how to fully reproduce. It calls internal APIs on TPL types. I also do not want to reproduce all of that.
Here is a short repro if you want to play with it:
static void Main()
{
Run().Wait();
}
static async Task Run()
{
Task[] tasks = new[] { CreateTask("ex1"), CreateTask("ex2") };
await Task.WhenAll(tasks);
}
static Task CreateTask(string message)
{
return Task.Factory.StartNew(() => { throw new Exception(message); });
}
Only one of the two exceptions is thrown in Run.
Note, that other questions on Stack Overflow do not address this specific problem. Please be careful when suggesting duplicates.
I disagree with the implication in your question title that await's behavior is undesired. It makes sense in the vast majority of scenarios. In a WhenAll situation, how often do you really need to know all of the error details, as opposed to just one?
The main difficulty with AggregateException is the exception handling, i.e., you lose the ability to catch a particular type.
That said, you can get the behavior you want with an extension method:
public static async Task WithAggregateException(this Task source)
{
try
{
await source.ConfigureAwait(false);
}
catch
{
// source.Exception may be null if the task was canceled.
if (source.Exception == null)
throw;
// EDI preserves the original exception's stack trace, if any.
ExceptionDispatchInfo.Capture(source.Exception).Throw();
}
}
I know I'm late but i found this neat little trick which does what you want. Since the full set of exceptions are available with on awaited Task, calling this Task's Wait or a .Result will throw an aggregate exception.
static void Main(string[] args)
{
var task = Run();
task.Wait();
}
public static async Task Run()
{
Task[] tasks = new[] { CreateTask("ex1"), CreateTask("ex2") };
var compositeTask = Task.WhenAll(tasks);
try
{
await compositeTask.ContinueWith((antecedant) => { }, TaskContinuationOptions.ExecuteSynchronously);
compositeTask.Wait();
}
catch (AggregateException aex)
{
foreach (var ex in aex.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
}
static Task CreateTask(string message)
{
return Task.Factory.StartNew(() => { throw new Exception(message); });
}
Here is a shorter implementation of Stephen Cleary's WithAggregateException extension method:
public static async Task WithAggregateException(this Task source)
{
try { await source.ConfigureAwait(false); }
catch when (source.IsCanceled) { throw; }
catch { source.Wait(); }
}
public static async Task<T> WithAggregateException<T>(this Task<T> source)
{
try { return await source.ConfigureAwait(false); }
catch when (source.IsCanceled) { throw; }
catch { return source.Result; }
}
This approach is based on a suggestion by Stephen Toub in this API proposal in GitHub.
Update: I added a special handling of the cancellation case, to prevent the awkwardness of propagating an AggregateException that contains an OperationCanceledException. Now the OperationCanceledException is propagated directly, and the Task.IsCanceled status is preserved. Kudos to #noseratio for pointing out this flaw in the comments of this answer. Of course now this implementation is not much shorter than Stephen Cleary's approach!
Exception Handling (Task Parallel Library)
I could say more but it would just be padding. Play with it, it does work as they say. You just have to be careful.
maybe you want this
God (Jon Skeet) explains await exception handling
(personally i shy away from await, but thats just my preference)
in response to comments (too long for a comment reply)
Then use threads as your starting point for an analogous argument as the best practises there will be the source of ones for here.
Exceptions happily get swallowed unless you implement code to pass them out (for instance the async pattern that the await is preumably wrapping ... you add them to an event args object when you raise an event). When you have a scenario where you fire up an arbitrary number of threads and execute on them you have no control over order or the point at which you terminate each thread. Moreover you would never use this pattern if an error on one was relevant to another. Therefor you are strongly implying that execution of the rest is completley independent - IE you are strongly implying that exceptions on these threads have already been handled as exceptions. If you want to do something beyond handling exceptions in these threads in the threads they occur in (which is bizzarre) you should add them to a locking collection that is passed in by reference - you are no longer considering exceptions as exceptions but as a piece of information - use a concurrent bag, wrap the exception in the info you need to identify the context it came from - which would of been passed into it.
Don't conflate your use cases.
I don't want to give up the practice to only catch the exceptions I expect. This leads me to the following extension method:
public static async Task NoSwallow<TException>(this Task task) where TException : Exception {
try {
await task;
} catch (TException) {
var unexpectedEx = task.Exception
.Flatten()
.InnerExceptions
.FirstOrDefault(ex => !(ex is TException));
if (unexpectedEx != null) {
throw new NotImplementedException(null, unexpectedEx);
} else {
throw task.Exception;
}
}
}
The consuming code could go like this:
try {
await Task.WhenAll(tasks).NoSwallow<MyException>();
catch (AggregateException ex) {
HandleExceptions(ex);
}
A bone-headed exception will have the same effect as in synchronous world, even in case it is thrown concurrently with a MyException by chance. The wrapping with NotImplementedException helps to not loose the original stack trace.
Extension that wraps original aggregation exception and doesn't change return type, so it can still be used with Task<T>
public static Task<T> UnswallowExceptions<T>(this Task<T> t)
=> t.ContinueWith(t => t.IsFaulted ? throw new AggregateException("whatever", t.Exception) : t.Result);
Example:
Task<T[]> RunTasks(Task<T>[] tasks) =>
Task.WhenAll(CreateSometasks()).UnswallowExceptions();
try
{ var result = await CreateTasks(); }
catch(AggregateException ex) { } //ex is original aggregation exception here
NOTE This method will throw if task was canceled, use another approach if cancelling is important for you