C# in Depth 2nd edition: ebook available, but soon to be updated

Just a quick interrupt while I know many of you are awaiting more asynchronous fun…

Over the weekend, the ebook of C# in Depth 2nd edition went out – and a mistake was soon spotted. Figure 2.2 was accidentally replaced by figure 13.1. I’ve included it in the book’s errata but we’re hoping to issue another version of the ebook shortly. Fortunately this issue only affects the ebook version – the files shipped to the printer are correct. Speaking of which, I believe the book should come off the printing press some time this week, so it really won’t be much longer before you can all physically scribble in the margins.

We’re going to give it a couple of days to see if anything else is found (and I’m going to check all the figures to see if the same problem has manifested itself elsewhere) – but I expect we’ll be issuing the second "final" version of the ebook late this week.

EDIT: A lot of people have asked about an epub/mobi version of the ebook. I don’t have any dates on it, but I know it’s something Manning is keen on, and there’s a page declaring that all new releases will have an epub/mobi version. I’m not sure how that’s all going to pan out just yet, but rest assured that it’s important to me too.

Control flow redux: exceptions in asynchronous code

Warning: as ever, this is only the result of reading the spec and experimentation. I may well have misinterpreted everything. Eric Lippert has said that he’ll blog about exceptions soon, but I wanted to put down my thoughts first, partly to see the difference between what I’ve worked out and what the real story is.

So far, I’ve only covered "success" cases – where tasks complete without being cancelled or throwing exceptions. I’m leaving cancellation for another time, but let’s look at what happens when exceptions are thrown by async methods.

What happens when an async method throws an exception?

There are three types of async methods:

  • Ones that are declared to return void
  • Ones that are declared to return Task
  • Ones that are declared to return Task<T> for some T

The distinction between Task and Task<T> isn’t important in terms of exceptions. I’ll call async methods that return Task or Task<T> taskful methods, and ones that return void taskless methods. These aren’t official terms and they’re not even nice terms, but I don’t have any better ones for the moment.

It’s actually pretty easy to state what happens when an exception is thrown – but the ramifications are slightly more complicated:

  • If code in a taskless method throws an exception, the exception propagates up the stack
  • If code in a taskful method throws an exception, the exception is stored in the task, which transitions to the faulted state
    • If we’re still in the original context, the task is then returned to the caller
    • If we’re in a continuation, the method just returns

The inner bullet points are important here. At any time it’s executing, an async method is either still in its original context – i.e. the caller is one level up the stack – or it’s in a continuation, which takes the form of an Action delegate. In the latter case, we must have previously returned control to the caller, usually returning a task (in the "taskful method" case).

This means that if you call a taskful method, you should expect to be given a task without an exception being thrown. An exception may well be thrown if you wait for the result of that task (possibly via an await operation) but the method itself will complete normally. (Of course, there’s always the possibility that we’ll run out of memory while constructing the task, or other horrible situations. I think it’s fair to classify those as pathological and ignore them for most applications.)

A taskless method is much more dangerous: not only might it throw an exception to the original caller, but it might alternatively throw an exception to whatever calls the continuation. Note that it’s the awaiter that gets to determine that for any await operation… it may be an awaiter which uses the current SynchronizationContext for example, or it may be one which always calls the continuation on a new thread… or anything else you care to think of. In some cases, that may be enough to bring down the process. Maybe that’s what you want… or maybe not. It’s worth being aware of.

Here’s a trivial app to demonstrate the more common taskful behaviour – although it’s unusual in that we have an async method with no await statements:

using System;
using System.Threading.Tasks;

public class Test
{
    static void Main()
    {
        Task task = GoBangAsync();
        Console.WriteLine("Method completed normally");
        task.Wait();
    }
    
    static async Task GoBangAsync()
    {
        throw new Exception("Bang!");
    }
}

And here’s the result:

Method completed normally

Unhandled Exception: System.AggregateException: One or more errors occurred. 
        —> System.Exception: Bang!
   at Test.<GoBangAsync>d__0.MoveNext()
   — End of inner exception stack trace —
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at Test.Main()

As you can see, the exception was only thrown when we waited for the asynchronous task to complete – and it was wrapped in an AggregateException which is the normal behaviour for tasks.

If an awaited task throws an exception, that is propagated to the async method which was awaiting it. You might expect this to result in an AggregateException wrapping the original AggregateException and so on, but it seems that something is smart enough to perform some unwrapping. I’m not sure what yet, but I’ll investigate further when I get more time. EDIT: I’m pretty sure it’s the EndAwait code used when you await a Task or Task<T>. There’s certainly no mention of AggregateException in the spec, so I don’t believe the compiler-generated code does any of this.

How eagerly can we validate arguments?

If you remember, iterator blocks have a bit of a usability problem when it comes to argument validation: because the iterator code is only run when the caller first starts iterating, it’s hard to get eager validation. You basically need to have one non-iterator-block method which validates the arguments, then calls the "real" implementation with known-to-be valid arguments. (If this doesn’t ring any bells, you might want to read this blog post, where I’m coming up with an implementation of LINQ’s Where method.)

We’re in a similar situation here, if we want arguments to be validated eagerly, causing an exception to be thrown directly to the caller. As an example of this, what would you expect this code to do? (Note that it doesn’t involve us writing any async methods at all.)

using System;
using System.Net;
using System.Threading.Tasks;

class Test
{
    static void Main()
    {
        Uri uri = null;
        Task<string> task = new WebClient().DownloadStringTaskAsync(uri);
    }
}

It could throw an exception eagerly, or it could set the exception into the return task. In many cases this will have a very similar effect – if you call DownloadStringTaskAsync as part of an await statement, for example. But it’s something you should be aware of anyway, as sometimes you may well want to call such methods outside the context of an async method.

In this particular case, the exception is thrown eagerly – so even though we’re not trying to wait on a task, the above program blows up. So, how could we achieve the same thing?

First let’s look at the code which wouldn’t work:

// This will only throw the exception when the caller waits
// on the returned task.
public async Task<string> DownloadStringTaskAsync(Uri uri)
{
    if (uri == null)
    {
        throw new ArgumentNullException("uri");
    }
        
    // Good, we’ve got an argument… now we can use it.
    // Real implementation goes here.
    return "Just a dummy implementation";
}

The problem is that we’re in an async method, so the compiler is writing code to catch any exceptions we throw, and propagate them through the task instead. We can get round this by using exactly the same trick as with iterator blocks – using a first non-async method which then calls an async method after validating the arguments:

public Task<string> DownloadStringTaskAsync(Uri uri)
{
    if (uri == null)
    {
        throw new ArgumentNullException("uri");
    }
        
    // Good, we’ve got an argument… now we can use it.
    return DownloadStringTaskAsyncImpl(uri);
}
    
public async Task<string> DownloadStringTaskAsyncImpl(Uri uri)
{
    // Real implementation goes here.
    return "Just a dummy implementation";
}

There’s a nicer solution though – because C# 5 allows us to make anonymous functions (anonymous methods or lambda expressions) asynchronous too. So we can create a delegate which will return a task, and then call it:

public Task<string> DownloadStringTaskAsync(Uri uri)
{
    if (uri == null)
    {
        throw new ArgumentNullException("uri");
    }
        
    // Good, we’ve got an argument… now we can use it.
    Func<Task<string>> taskBuilder = async delegate {
        // Real implementation goes here.
        return "Just a dummy implementation";
    };
    return taskBuilder();
}

This is slightly neater for methods which don’t need an awful lot of code. For more involved methods, it’s quite possibly worth using the "split the method in two" approach instead.

Conclusion

The general exception flow in asynchronous methods is actually reasonably straightforward – which is a good job, as normally error handling in asynchronous flows is a pain.

You need to be aware of the consequences of writing (or calling) a taskless asynchronous method… I expect that the vast majority of asynchronous methods and delegates will be taskful ones.

Finally, you also need to work out when you want exceptions to be thrown. If you want to perform argument validation, decide whether it should throw exceptions eagerly – and if so, use one of the patterns shown above. (I haven’t spent much time thinking about which approach is "better" yet – I generally like eager argument validation, but I also like the consistency of all errors being propagated through the task.)

Next up: dreaming of multiple possibly faulting tasks.