Async/await in C#: pitfalls



I’d like to discuss some common pitfalls of async/await feature in C# and provide you with workarounds for them.

How async/await works

The internals of async/await feature are very well described by Alex Davies in his book, so I will only briefly explain it here. Consider the following code example:

public async Task ReadFirstBytesAsync(string filePath1, string filePath2)

{

    using (FileStream fs1 = new FileStream(filePath1, FileMode.Open))

    using (FileStream fs2 = new FileStream(filePath2, FileMode.Open))

    {

        await fs1.ReadAsync(new byte[1], 0, 1); // 1

        await fs2.ReadAsync(new byte[1], 0, 1); // 2

    }

}

This function reads the first bytes from two files passed in (I know, it’s quite a synthetic example). What would happen at “1” and “2” lines? Will they execute simultaneously? No. What will happen is this function will actually be split by “await” keyword in three pieces: the part before the “1” line, the part between “1” and “2” lines and the part after the “2” line.

The function will create a new I/O bound thread at the line “1”, pass it the second part of itself (which is between “1” and “2” lines) as a callback and return the control to the caller. After the I/O thread completes, it calls the callback, and the method continues executing. It creates the second I/O thread at the line “2”, passes the third part of itself as a callback and returns again. After the second I/O thread completes it calls the rest part of the method.

The magic is here because of the compiler rewriting the code of the method marked as async to a state machine, just like it does with iterators.

When to use async/await?

There are two major cases in which using async/await feature is preferred.

First of all, it can be used in thick clients to deliver better user experience. When a user presses a button starting a heavy operation, it’s better to perform this operation asynchronously without locking the UI thread. Such logic required a lot of effort to be implemented before .NET 4.5 had been released. Here how it can look now:

private async void btnRead_Click(object sender, EventArgs e)

{

    btnRead.Enabled = false;

 

    using (FileStream fs = new FileStream(“File path”, FileMode.Open))

    using (StreamReader sr = new StreamReader(fs))

    {

        Content = await sr.ReadToEndAsync();

    }

 

    btnRead.Enabled = true;

}

Note that Enabled flag is changed by the UI thread in both cases. This approach removes the necessity of such ugly code:

if (btnRead.InvokeRequired)

{

    btnRead.Invoke((Action)(() => btnRead.Enabled = false));

}

else

{

    btnRead.Enabled = false;

}

In other words, all the “light” code is executed by the called thread, whereas “heavy” code is delegated to a separate thread (I/O or CPU-bound). This approach allows us to significantly reduce the amount of work required to synchronize access to UI elements as they are always managed by the UI thread only.

Secondly, async/await feature can be used in web applications for better thread utilization. ASP.NET MVC team have done a lot of work making asynchronous controllers easy to implement. You can just write an action like the following and ASP.NET will do all of the rest work.

public class HomeController : Controller

{

    public async Task<string> Index()

    {

        using (FileStream fs = new FileStream(“File path”, FileMode.Open))

        using (StreamReader sr = new StreamReader(fs))

        {

            return await sr.ReadToEndAsync(); // 1

        }

    }

}

At the example above the worker thread executing the method starts a new I/O thread at the line “1” and returns to the thread pool. After the I/O thread finishes working, a new thread from the thread pool is picked up to continue method execution. Hence, CPU-bound threads from the thread pool are utilized more economically.

Async/await in C#: pitfalls

If you are developing a third-party library, it is always vital to configure await in such a way that the rest of the method will be executed by an arbitrary thread from the thread pool. First of all, third party libraries (if they are not UI libraries) usually don’t work with UI controls, so there’s no need to bind the UI thread. You can slightly increase performance by allowing CLR to execute your code by any thread from the thread pool. Secondly, by using the default implementation (or explicitly writing ConfigureAwait(true)), you leave a hole for possible deadlocks. Moreover, client code won’t be able to change this implementation. Consider the following example:

private async void button1_Click(object sender, EventArgs e)

{

    int result = DoSomeWorkAsync().Result; // 1

}

 

private async Task<int> DoSomeWorkAsync()

{

    await Task.Delay(100).ConfigureAwait(true); //2

    return 1;

}

A button click leads to a deadlock here. The UI thread starts a new I/O thread at “2” and falls to sleep at “1”, waiting for I/O work to be completed. After the I/O thread is done, it dispatches the rest of the DoSomeWorkAsync method to the thread the method was called by. But that thread is waiting for the method completion. Deadlock.

ASP.NET will behave the same way, because although ASP.NET doesn’t have a single UI thread, the code in controllers’ actions can’t be executed by more than one thread simultaneously.

Of course, you could use await keyword instead of calling the Result property to avoid the deadlock:

private async void button1_Click(object sender, EventArgs e)

{

    int result = await DoSomeWorkAsync();

}

 

private async Task<int> DoSomeWorkAsync()

{

    await Task.Delay(100).ConfigureAwait(true);

    return 1;

}

But there is still one case where you can’t avoid deadlocks. You can’t use async methods in ASP.NET child actions, because they are not supported. So you will have to access the Result property directly and will get a deadlock if the async method your controller calls didn’t configure Awater properly. For example, if you do something like the following and the SomeAction action calls an async method’s Result property and that method is not configured by ConfigureAwait(false) statement, you’ll get a deadlock.

@Html.Action(SomeAction, SomeController)

Clients of your library won’t be able to change its code (unless they decompile it), so always put ConfigureAwait(false) in your async methods.

How you definitely shouldn’t use PLINQ and async/await

Look at the example:

private async void button1_Click(object sender, EventArgs e)

{

    btnRead.Enabled = false;

    string content = await ReadFileAsync();

    btnRead.Enabled = true;

}

 

private Task<string> ReadFileAsync()

{

    return Task.Run(() => // 1

    {

        using (FileStream fs = new FileStream(“File path”, FileMode.Open))

        using (StreamReader sr = new StreamReader(fs))

        {

            return sr.ReadToEnd(); // 2

        }

    });

}

Is this code asynchronous? Yes. Is it a correct way to write asynchronous code? No. The UI thread starts a new CPU-bound thread at “1” and returns. The new thread then starts a new I/O thread at “2” and falls to sleep waiting for its completion.

So, what happens here? Instead of creating just an I/O thread we create both CPU thread at “1” and I/O thread at “2”. It’s a waste of threads. To fix it, we need to call the async version of the Read method:

private Task<string> ReadFileAsync()

{

    using (FileStream fs = new FileStream(“File path”, FileMode.Open))

    using (StreamReader sr = new StreamReader(fs))

    {

        return sr.ReadToEndAsync();

    }

}

Here is another example:

public void SendRequests()

{

    _urls.AsParallel().ForAll(url =>

    {

        var httpClient = new HttpClient();

        httpClient.PostAsync(url, new StringContent(“Some data”));

    });

}

Looks like we are sending requests in parallel, right? Yes, we are, but there’s the same issue we had before: instead of creating just an I/O thread we create both I/O and CPU-bound threads for every request. To fix the code we need to use Task.WaitAll method:

public void SendRequests()

{

    IEnumerable<Task> tasks = _urls.Select(url =>

    {

        var httpClient = new HttpClient();

        return httpClient.PostAsync(url, new StringContent(“Some data”));

    });

    Task.WaitAll(tasks.ToArray());

}

Is it always necessary to run IO operations without binding CPU-bound threads?

Well, it depends. Sometimes it’s impossible to do it, sometimes it brings too much of complexity. For example, NHibernate doesn’t implement asynchronous data fetching. EntityFramework, on the other hand, does, but there might not be a lot of sense in using it in some cases. You should always consider pros and cons of every design decision.

Also, thick clients (like WPF or WinForms) usually don’t have a lot of load, so there’s actually no difference in choosing one approach over another. But anyway, you should always know what is happening under the cover so you could make a conscious decision in every single case.

Further reading

Share




  • http://codepattern.net/ CodePattern.net

    Very Informative!

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Thanks! I’m glad it was intresting

  • Eric Rasmussen

    Thank you for this post! I’ve apparently been wasting CPU threads here and there since discovering the convenience of async/await. Time to go update some code… 🙂

  • Sergey Ostashev

    Thank you for the article. It’s made things about async/await much more clear. But will you please clarify one thing about thread creation when using async/await.

    In MSDN I read: “The async and await keywords don’t cause additional threads to be created.” So, let’s assume that DoSomeWorkAsync() in your example does something that requires no I/O operations and it does not create any CPU-bound threads explicitly (e.g., by Task.Run). Say, it runs through enormous array just sorting it, that takes a lot of time. Will there be any background threads created by this code: int result = await DoSomeWorkAsync();

    Thank you beforehand.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Thank you!

      In this case, there’d be no threads created. As there is no “await” keyword, the method itself runs synchronously even if it is marked with “async” keyword.

      Also, there’s no way to find out whether or not any threads are created when you call a “Task” method. To do so, you need to either read the documentation or look at the source code. There are some guidelines, though. It is considered a good practice to not return a Task object if a method doesn’t do any asynchronous (or parallel) work, so chances are that if you see some Task DoSomeWorkAsync() method, it does perform some work asynchronously. But again, it’s only a convention, no restrictions are applied at the compiler level.

  • disqus_kCEFVbbFLB

    the real problem is asynchronous execution in a synchronous context, async operation is a monad, once in a monad you can’t get out of there without breaking some laws, people who start an async operation and return a sync (other than Task) result are criminals because the lose track of when an async operation is going to land and this creates race conditions which are a major pain to troubleshoot

  • Karl Gjertsen

    Great article and clears up a few questions I had, thank you.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Thanks! Glad you found it interesting.

  • Roman Perchurov

    How I can configurate async/await action in ASP.NET MVC for applying it in @Html.Action()? Is it possible?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      As far as I know, it’s impossible to use async operations in ASP.NET’s child actions, they always dead lock. You’ll need to introduce non-async versions to do that (i.e. call MethodAsync().Result in your child actions).