Async, Exceptions and Library Design

Hat tip to Stephen Toub for discussing this with me and helping to describe the solution.

At my CodeMash precompiler, I mentioned how the C# compiler ensures that methods marked with the 'async' keyword that contain 'await' expressions never throw synchronous exceptions. Instead, those methods will return a Task (or Task<T>) that will be placed in the faulted state if the method throws an exception. The compiler does the work to add the appropriate try/catch clauses to your methods, and translates any exceptions thrown during your method's execution into returned faulted task.

Jon Skeet was concerned by this strategy. He felt that API designers would prefer throwing synchronous exceptions for obvious programmer errors (things like ArgumentNullException and so on). I mentioned that the language specification defines that async methods will returned faulted tasks; they will not throw synchronous exceptions.

Of course, there is a way around this. You have to separate your public async APIs into two pieces: A public synchronous API that does parameter validation and state validation. This synchronous method then calls an internal async method that does the asynchronous work.

As an example, consider this (rather contrived) async method:

 

      
        
          
            
public
          
        
         async Task<string> FizzBuzzAsync(int val)
{
    if (val <= 0)
        throw new ArgumentException("We can't fizzbuzz negative numbers, or 0");

    await Task.Delay(250);
    var rVal = string.Empty;
    if (val % 3 == 0)
        rVal += "Fizz";
    if (val % 5 == 0)
        rVal += "Buzz";
    if (string.IsNullOrWhiteSpace(rVal))
        rVal = val.ToString();
    return rVal;
}

 

Calling this method with a negative number is a programming error. We'd like to have that condition throw a synchronous exception. We can achieve this by separating the method into two parts. The first part is a synchronous method that performs the parameter validation and state validation. The second part is an internal method that performs the asynchronous work.  The first method will throw exceptions synchronously. The second will report errors using a faulted task.

 

      
        
          
            public
          
        
         Task<string> FizzBuzzAsync(int val)
{
    if (val <= 0)
        throw new ArgumentException("We can't fizzbuzz negative numbers, or 0");
 
    return FizzBuzzAsyncImpl(val);
}
 
private static async Task<string> FizzBuzzAsyncImpl(int val)
{
    await Task.Delay(250);
    var rVal = string.Empty;
    if (val % 3 == 0)
        rVal += "Fizz";
    if (val % 5 == 0)
        rVal += "Buzz";
    if (string.IsNullOrWhiteSpace(rVal))
        rVal = val.ToString();
    return rVal;
}

That ensures that your public async methods conform to both important rules.

First, TAP (Task Asynchronous Pattern) methods do not allow synchronous runtime exceptions. They must return errors by returning a faulted task.

Second, to make it easier for callers to detect and correct programming errors, simple programming errors will throw synchronous exceptions.

Created: 3/5/2013 4:49:13 PM

Current Projects

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.

I'm also the president of Humanitarian Toolbox. We build Open Source software that supports Humanitarian Disaster Relief efforts. We'd appreciate any help you can give to our projects. Look at our GitHub home page to see a list of our current projects. See what interests you, and dive in.

Or, if you have a group of volunteers, talk to us about hosting a codeathon event.