C# await inside catch and finally leads to some interesting semantics

I remember when await was introduced into C# – the feature took us away from the callback hell that was developing, but it also required some understanding of the underlying code generation to really see what was going on in simple looking code. The abstraction led to a few rather confusing effects, such as always throwing the first exception of an AggregateException in the await.

At the time people were interested in why you couldn’t use await inside catch or finally blocks, and the answer always came down to confusing semantics. In the recently released C# 6, await is now available in catch and finally blocks, so the question is how this has been achieved without breaking the existing semantics. And I think the answer is that the semantics of some C# forms has now been changed in a way that again requires knowledge of the underlying transforms to understand.

Let’s take the async version of the standard thread abort construct. The call to Abort() notionally throws a ThreadAbortException which can be caught and processed by catch and finally blocks, but which has the interesting semantic of being re-raised when the processing block finishes (unless you reset the abort).

static async Task AsyncVersion()
{
  try
{
Thread.CurrentThread.Abort();
}
finally
{
Console.WriteLine(“Can’t stop me!!”);
}
Console.WriteLine(“After abort”);
}

We can explain the behavior of this code fairly easily. The Abort throws the ThreadAbortException, which is caught by the finally block. This finally block prints “Can’t stop me!!” and then the exception is rethrown when the finally block finishes. Hence the “After abort” is never printed.

Now change the finally block to

finally
{
Console.WriteLine(“Can’t stop me!!”);
await Task.Run(() => Console.WriteLine(“Next”));
}

When you run this version of the code nothing is printed, and it’s hard to understand why without looking at the code generation. The async method is translated to a class that implements a state machine and, crucially, the finally is no longer a finally in the generated IL code, but is instead translated to a catch block that handles all exceptions.

Reflector shows us:

try
{
Thread.CurrentThread.Abort();
}
catch (object obj1)
{
obj2 = obj1;
this.<>s__1 = obj2;
}

ie the finally isn’t processed as a CLR finally, but is instead translated to a catch which re-enters the state machine processing. Of course, this means that the ThreadAbort exception which is rethrown at the end of any catch block is going to be rethrown too early, avoiding the exception of the code in the finally block.

I can see that it is a benefit to be able to await in catch and finally blocks, and I guess we’ve had to accept in the past that C# is not the assembly language of .NET. However, the code generation approach to implementing these high level constructs means that sometimes we can see through the abstraction as we can here. I’m not quite sure how important that is.

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s