C# Async Tips & Tricks

The following are few tips and tricks for working with async in C#. This is as much a reference for myself as anyone else, but I figured it would probably useful for others as well.

Run sync method as "async"

Task.Run(() => DoSyncStuff());  

Technically, this is fake async. It's still blocking, but it runs on a background thread. This is really only useful to keep from blocking the UI thread with desktop/mobile applications. In a web application context, this is pretty much pointless, since every thread comes from the same pool for serving requests that the main (request) thread came from and the response won't be returned until everything is done anyways.

Run async method as sync

For this one we have a helper class borrowed from Microsoft. It appears is various namespaces, but always as an internal, so you can't just use it directly from the framework.

public static class AsyncHelper  
{
    private static readonly TaskFactory _taskFactory = new
        TaskFactory(CancellationToken.None,
                    TaskCreationOptions.None,
                    TaskContinuationOptions.None,
                    TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        => _taskFactory
            .StartNew(func)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    public static void RunSync(Func<Task> func)
        => _taskFactory
            .StartNew(func)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

Then

AsyncHelper.RunSync(() => DoAsyncStuff());  


Discarding the context

When you await an asynchronous operation, the context of the calling code is passed along by default. This can have a not insignificant performance impact. If you don't need to restore that context later, then it's just wasted resources. You can prevent this behavior by appending ConfigureAwait(false) to your call:

await DoSomethingAsync().ConfigureAwait(false);  

You should pretty much always do this, unless there's a specific reason to keep the context. Certain scenarios for that include when you need access to a particular GUI component or you need to return a response from a controller action.

Importantly, though, each operation has it's own context, so you can safely use ConfigureAwait(false) inside an asynchronous method called by code that needs to maintain the context; you just wouldn't be able to use ConfigureAwait(false) on that method itself. For example:

public async Task<IActionResult> Foo()  
{
    // No `ConfigureAwait(false)` here
    await DoSomethingAsync();
    return View();
}

...

public async Task DoSomethingAsync()  
{
    // This is fine
    await DoSomethingElseAsync().ConfigureAwait(false);
}

As a result, you can and should factor out multiple asynchronous operations where context needs to be maintained into a separate method, so you're only need to conserve the context once, instead of N times. For example:

public async Task<IActionResult> Foo()  
{
    await DoFirstThingAsync();
    await DoSecondThingAsync();
    await DoThirdThingAsync();
    return View();
}

Here, each of these operations get a copy of the calling code's context, and since we need that context, using ConfigureAwait(false) is not an option. However, by refactoring to the following code, we'd only need a single copy of the calling code's context.

public async Task DoThingsAsync()  
{
    await DoFirstThingAsync().ConfigureAwait(false);
    await DoSecondThingAsync().ConfigureAwait(false);
    await DoThirdThingAsync().ConfigureAwait(false);
}

public async Task<IActionResult> Foo()  
{
    await DoThingsAsync();
    return View();
}


Async and garbage collection

In synchronous code, local variables go on the stack and are discarded when they go out of scope. However, because of the context switching that occurs when you await an async operation, those local variables have to be preserved. The Framework does this by adding them to a struct that goes on the heap. In this way, when execution is returned to the calling code, the locals can be restored. However, the more you have going on in your code, the more has to be added to the heap, resulting in more frequent GC cycles. Some of this may be unavoidable, but you should watch for useless variable assignments when you're going to be awaiting an async operation. For example, code like:

var today = DateTime.Today;
var todayString = today.ToString("MMMM d, yyyy");

This will cause two different values to go into the heap, whereas if all you need is todayString, you can reduce that to one value simply by rewriting the code as:

var todayString = DateTime.Today.ToString("MMMM d, yyyy");

It's one of those things you don't think about unless someone tells you.

Cancelling async work

One of the benefits of async in C# is that tasks can be cancelled. This allows you to abort a task if user cancels it in the UI, navigates away from a web pages, etc. To enable cancellation, your asynchronous methods should accept a CancellationToken parameter.

public async Task DoSomethingAsync(CancellationToken cancellationToken)  
{
    ...
}

That cancellation token, then, should be passed into any other asynchronous operations the method calls. It is the responsibility of the method to enable cancellation, if it can and desires to. Not all asynchronous tasks can be cancelled. Generally speaking, whether a task can be cancelled or not depends on whether the method has an overload that accepts CancellationToken.

Cancelling tasks that can't be cancelled

In some scenarios, you can still cancel a task if the method doesn't provide an overload that accepts CancellationToken. You're not truly cancelling the task, but depending on the implementation, you may be able to abort it, nonetheless, effectively having the same result. For example, the ReadAsStringAsync method of HttpContent does not have an overload that accepts CancellationToken. However, if you dispose of the HttpResponseMessage, then the attempt to read the content will be aborted.

try  
{
    using (var response = await httpClient.GetAsync(new Uri("https://www.google.com")))
    using (cancellationToken.Register(response.Dispose))
    {
        return await response.Content.ReadAsStringAsync();
    }
}
catch (ObjectDisposedException)  
{
    if (cancellationToken.IsCancellationRequested)
        throw new OperationCanceledException();

    throw;
}

Essentially, we're using the CancellationToken to call Dispose on the HttpResponseMessage instance, if it's cancelled. That will then cause ReadAsStringAsync to throw an ObjectDisposedException. We catch this exception, and if the CancellationToken has been canceled, we throw an OperationCanceledException instead.

The key to this approach is really in the ability to dispose of some parent object that will cause the method that can't be cancelled to raise an exception. It's not applicable to everything, but can give you an out in certain scenarios.

Eliding the async/await keywords

An asynchronous method can be written in either of the following ways:

public async Task FooAsync()  
{
    await DoSomethingAsync();
}

public Task BarAsync()  
{
    return DoSomethingAsync();
}

In the first, the asynchronous operation is awaited in the method, and the result is then wrapped in another task before being returned to the calling code. In the second, the task for the asynchronous operation is returned directly. If you have an asynchronous method that only calls another asynchronous method (as is often the case with asynchronous overloads), then you should elide the async/await keywords, as in the second method above.

Handle exceptions in async methods

Methods with the async keyword can safely throw exceptions. The compiler will take care of wrapping the exception in a Task.

public async Task FooAsync()  
{
    // This is fine
    throw new Exception("All your bases are belong to us.");
}

However, Task-returning methods without the async keyword should return a Task with the exception.

public Task FooAsync()  
{
    try
    {
        // Code that throws exception
    }
    catch (Exception e)
    {
        return Task.FromException(e);
    }
}


Reducing duplicate code when implementing sync and async versions of a method

Often when developing sync and async versions of a method, you'll find that the only real difference between the two implementations is that one calls async versions of various methods while the other calls the sync versions. When the implementation is virtually the same, except for the use of async/await, there's various "hacks" you can utilize to factor out the duplicate code. The best and least "hacky" method I've found is referred to as the "Flag Argument Hack". Essentially, you introduce a boolean that indicates whether the method should use sync or async access and then branch accordingly:

private async Task<string> GetStringCoreAsync(bool sync, CancellationToken cancellationToken)
{
    return sync
        ? SomeLibrary.GetString()
        : await SomeLibrary.GetStringAsync(cancellationToken).ConfigureAwait(false);
}

public string GetString()
    => GetStringCoreAsync(true, CancellationToken.None)
        .ConfigureAwait(false)
        .GetAwaiter()
        .GetResult();

public Task<string> GetStringAsync()
    => GetStringAsync(CancellationToken.None);

public Task<string> GetStringAsync(CancellationToken cancellationToken)
    => GetStringCoreAsync(false, cancellationToken);

That seems like a lot of code, so let's unwrap it a bit. First, we have a private method GetStringCoreAsync. This is where we factor out our common code. Here, we're just calling into some other library that has sync and async methods to get some sort of string. Admittedly, for something this simplistic, you really shouldn't employ this hack, but should rather just have each method call its appropriate counterpart directly. However, I didn't want to hinder understanding by introducing an overly complex implementation. As you can see, the main point here is that we're branching on the value of sync to either utilize the sync or async method from the library. This will work fine as long as you await the async method, which then means that this private method needs to have the async keyword. We're also passing in a CancellationToken in case the async methods employed inside are cancellable.

Next, we just have our sync and async implementations which merely call the private method. For the sync version, we need to unwrap the Task being returned from the private method. To do that, we use the GetAwaiter().GetResult() pattern to safely block on the async call. There's no danger of a deadlock here, because while the private method is async, when we pass true for sync, no async methods are actually employed. We also use ConfigureAwait(false) to prevent the synchronization context from being attached, since it's entirely unnecessary bloat: there's no possibility of thread-switching here.

The async implementation is fairly unremarkable. There's an overload that passes a default of CancellationToken.None in case no cancellation token is passed, and then the actual implementation just calls the private method with false for the sync parameter and includes the cancellation token.

There's a school of thought that says that methods shouldn't branch on booleans like this. If you have two separate sets of logic, then you should have two separate methods. There's some truth in that, but I think it has to be weighed against how different the logic actually is. As a result, this is a good way to factor out common code if you have a significant amount of logic that is mostly duplicated. However, it should be a last resort in this respect. If there's portions of the code that are CPU-bound or otherwise run synchronously, you should first try to factor out just these portions of code. You may still have some duplication between your sync and async methods, but if you can get most of the meat out into methods that are usable by either without resorting to hacks, then that's the optimal path.

There's also an argument to be made that if you have that much logic, your method might be doing too much in the first place. You'll have to let your own judgement rule. There are times when doing something like this is actually the best path, but you should carefully evaluate whether that's the case before simply employing this method.

Async in console applications

class Program  
{
    static void Main(string[] args)
    {
        MainAsync(args).GetAwaiter().GetResult();
    }

    static async Task MainAsync(string[] args)
    {
        // await something
    }
}

For what it's worth, C# 7.1 promises Async Main support, so you'd simply do:

class Program  
{
    static async Task Main(string[] args)
    {
        // await something
    }
}

However, at the time of writing, this doesn't work. It's really just syntactic sugar, though. When the compiler encounters an async Main, it simply wraps it in a regular synchronous Main, just like in the first code sample.

Ensuring that async doesn't block

There's whole lot of terminology that gets confused with async in C#. You hear that sync code blocks the thread, while async code does not. That's actually not true. Whether or not a thread is blocked, really has nothing at all to do with whether is sync or async. The only reason it comes into the discussion at all is that async is at least better than sync if your goal is to not block a thread, because sometimes, in some scenarios, it maybe might just run on a different thread. If you have any synchronous code in your async method (anything that's not awaiting something else) that code will always run sync. Further, an async operation may run sync if what it awaits on has already completed. Lastly, async doesn't ensure that the work won't actually be done on the same thread. It just opens up the possibility for a thread-switch.

Long and short, if you need to ensure that an async operation will not block the thread, such as for a desktop or mobile app where you want to keep the GUI thread open, then you should use:

Task.Run(() => DoStuffAsync());  

Wait. Isn't that the same thing we used above to run sync as "async"? Yep. The same principle applies: Task.Run will run the delegate you pass to it on a new thread. In turn, that means it won't run on the current thread.

Making a sync operation Task-compatible

Most async methods return Task, but not all Task-returning methods are necessarily async. That can be a bit of a mind-bender. Let's say you need to implement an a method that returns Task or Task<TResult>, but you don't actually have anything async to do.

public Task DoSomethingAsync(CancellationToken cancellationToken)  
{
    if (cancellationToken.IsCancellationRequested)
    {
        return Task.FromCanceled(cancellationToken);
    }

    try
    {
        DoSomething();
        return Task.FromResult(0);
    }
    catch (Exception e)
    {
        return Task.FromException(e);
    }
}

First, this ensures that the operation hasn't been canceled. If it has, a canceled task is returned. Then, the sync work we need to do is wrapped in a try..catch block. If an exception is thrown, we'll need to return a faulted task, which includes that exception. Finally, if it completes correctly, we return a completed task.

It's important to realize that this is not actually async. DoSomething is still sync and will block. However, now it can be handled as if it was async, because it returns a task, as it should. Why would you do this? Well, one example is when implementing an adapter pattern and one of the sources you're adapting to doesn't offer an async API. You still have to satisfy the interface, but you should comment the method to notate that it's not actually async. Those wanting to utilize this method in situations where they need to not block the thread can choose to call it via passing it as a delegate to Task.Run.

comments powered by Disqus