Avoid shooting yourself in the foot with Tasks and Async
Posted by Filip Ekberg on 20 Sep 2012
Since the release of .NET 4.5, you've been able to use the RTM version of Async & Await. There are some things though that can lead to very weird behaviors in your applications, and a lot of confusion. Kevin (Pilchie) over at Microsoft just gave me heads up on some of these and I thought that I would share it with the rest of you!
There was a very interesting discussion around this subject in the ##roslyn channel on freenode with @ermau and @jrusbatch.
Avoid `async void`
When you're doing asynchronous methods that just return void, there is no way to track when these methods are done.
Look at the following example:
class FooBar
{
public async void Biz()
{
await Foo();
}
public Task Foo()
{
return Task.Factory.StartNew(() =>
{
Task.Delay(2000);
Console.WriteLine("Done!");
});
}
}
If we create an instance of FooBar
and call Biz()
, there's no way for us to wait for the task to finish. Normally we would want a reference to a Task
that we could wait for to finish, but in this case we don't! Avoid async void
whenever you can.
Just change the method signature to async Task
instead and you will be able to do:
var foo = new FooBar();
foo.Biz().Wait();
The only reason you want to use async void
is when you have an event handler that needs to await
something. Such as a Click
event handler like this:
private async void MyButton_Clicked(object sender, RoutedEventArgs e)
{
var task = Task<string>.Factory.StartNew(() =>
{
Thread.Sleep(2000);
return string.Empty;
});
var data = await task;
MessageBox.Show(data);
}
Never getting that second exception when awaiting multiple results?
Consider that we have two asynchronous methods that both thrown an exception (almost at the same time), like these two methods here:
public static Task<int> Foo()
{
return Task<int>.Factory.StartNew(() => {
throw new Exception("From Foo!");
});
}
public static Task<int> Bar()
{
return Task<int>.Factory.StartNew(() =>
{
throw new Exception("From Bar!");
});
}
What would happen if we called the following method?
public static async Task<int> Caclulate()
{
return await Foo() + await Bar();
}
We would indeed get an exception thrown. But we would only get One exception thrown! In fact, the only exception that we will get is the First exception being thrown.
This means that we would not see an aggregated exception list as we might expect.
Exceptions in `Tasks` don't travel back to the caller
When working with multiple threads and the thread causes a problem that is unhandled, these are thrown back at the caller.
Look at this following example for instance:
new System.Threading.Thread(() => { throw new Exception(); }).Start();
When running this, we will see the following exception:
Unhandled Exception: System.Exception: Exception of type 'System.Exception' was thrown.
This is perfectly reasonable, it tears down the calling process!
But what about if this was a Task
instead?
The following code spawns a new task and just throws a similar exception to what the thread example did above:
Task.Factory.StartNew(() => { throw new Exception(); });
What happens when we run this? Nothing!
No exception were traveled back to the caller, this can cause potential confusions. This is actually by design and MSDN says the following about it:
In the .NET Framework 4, by default, if a `Task` that has an unobserved exception is garbage collected, the finalizer throws an exception and terminates the process. The termination of the process is determined by the timing of garbage collection and finalization. To make it easier for developers to write asynchronous code based on tasks, the .NET Framework 4.5 changes this default behavior for unobserved exceptions. Unobserved exceptions still cause the `UnobservedTaskException` event to be raised, but by default, the process does not terminate. Instead, the exception is ignored after the event is raised, regardless of whether an event handler observes the exception.
If we want the the .NET 4 behavior, we can re-enable the unobserved task exceptions by changing the applications app.config. Add the following to the app.config:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
Since the exception will be thrown in the finalizer, we can test this by running the following:
Task.Factory.StartNew(() => { throw new Exception(); });
Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
This will tear down the process as it did in .NET 4.0.
Unhandled Exception: System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.Exception: Exception of type 'System.Exception' was thrown.
Have you ever shot yourself in the foot with Tasks or Async? Leave your horror stories in the comment field!