Avoid shooting yourself in the foot with Tasks and Async

Posted by Filip Ekberg on September 20 2012 6 Comments

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!

Vote on HN

6 Responses to Avoid shooting yourself in the foot with Tasks and Async

  1. Pingback: Dew Drop – September 20, 2012 (#1,405) | Alvin Ashcraft's Morning Dew

  2. Adam RalphNo Gravatar says:

    Thanks for the post – some good things to bear in mind.

    An observation:-

    return await Foo() + await Bar();

    I don’t think I would ever expect an aggregate exception here. I would expect the exception from

    Foo()

    , since effectively we are waiting on the first call and then waiting on the second call.

  3. Pingback: Windows 8 Developer Links – 2012-09-21Dan Rigby | Dan Rigby

  4. AliNo Gravatar says:

    Good post. But I would not agree with first point.

    First of all, your code will run the next line of code and that is how you know it has finished, regardless of returning a value or not. For example instead of

    var data = await task;
    MessageBox.Show(data);

    You can use

    await task;
    MessageBox.Show("Task was done");

    Also there are many async operations in .NET Framework that do not return a value (i.e. they return Task instead of Task). For example, HttpContent.LoadIntoBufferAsync().

  5. Pingback: Reading Notes 2012-10-01 | Matricis

  6. Pingback: Don't deadlock with async and await

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>