Will it block? Debunking async/await pitfalls

With classes and methods such as Task.Run, Task.Factory.StartNew, TaskScheduler, SynchronizationContext, ThreadPool and others it’s very easy to get confused by all the ways to distribute work between the UI thread and background threads in the .NET framework using C#. However, if you follow some easy rules and avoid some pitfalls, the TPL (Task Parallel Library) and the async/await keywords greatly simplify development of reactive user interfaces.

A screenshot of the "Tasks" window in front of source code
Debugging parallel code

Why parallelization is important

The era of rapidly increasing clock speeds of CPUs has come to an end for some time now. Instead, parallel execution of code on multiple processor cores has become more common. However, with conventional programming languages and frameworks, writing robust and error-free parallel code is often not very easy.

In addition, many GUI frameworks (such as Windows Forms or WPF) are single threaded. This means that controls may only be accessed from the so-called UI-thread (otherwise an exception will be raised or undefined behavior might occur). In addition, using the UI-thread to perform long-running tasks will block the UI making it unresponsive. “Long-running” in this case is everything longer than 100ms. This means that you should avoid all kinds of network and file system access from the UI-thread to keep your UI responsive (remember that drives might need a couple of seconds to spin up after entering power save mode and the file system might also include network locations).

What is the TPL and why should you care?

As a result, code execution needs to switch between the UI thread (when accessing controls) and worker threads which perform the actual work (such as doing heavy calculations or accessing external resources). Back in the earlier days, this meant writing a lot of synchronization code. For example, Windows Forms provides the class BackgroundWorker for simple processing tasks involving progress reporting without having to use Control.BeginInvoke. Similarly, the WPF includes the concept of a Dispatcher which manages the execution of code within the UI thread.

.NET 4.0 introduced the so-called Task Parallel Library (TPL) which was extended by the introduction of the async and await keywords in .NET 4.5. Relying on the concept of a SynchronizationContext, those extensions enable you to write parallelized code including synchronization with the UI Thread while maintaining readability and reducing overhead.

So what’s the issue?

As discussed before, there are basically two important principles to consider when writing code for a responsive UI:

  • Never block the UI code (execute everything possibly taking longer than about 100ms in a background thread)
  • Only access UI elements (controls) from the UI thread. As a best practice, this includes changing data bound properties.

Sounds simple, doesn’t it? Well – the tricky part is to ensure that those rules are always followed and you don’t end up in the wrong kind of thread accidentally (attempting to access to a control from a background thread or blocking the UI thread).

There are some pitfalls you can easily fall for when using the TPL and async/await. But don’t worry – we will discuss the most common ones here. In addition, we will see how the choice of methods will influence which thread the code will execute on eventually. Let’s take a look, shall we?

Will it parallelize?

All the snippets below have been verified using a simple WPF application which you can find on GitHub. It allows you to call the snippets from the UI thread and prints debug messages about the thread which was used to execute certain lines (by comparing Thread.CurrentThread to the UI thread). For reference, the PrintInfo method which is used throughout the snippets is implemented this way:

private void PrintInfo(string marker)
{
   var currentThread = Thread.CurrentThread == this._uiThread ? "UI Thread" : "Threadpool Thread"; 
   Console.WriteLine("{0} ({1})", marker, currentThread);
}

Calling a method from an async one

public async void SimpleCallFromAsync()
{
   //UI Thread
   this.PrintInfo("SimpleCall"); 
}

A common misunderstanding regarding the async keyword is that it somehow makes a method execute on a different thread. That’s not true: Its presence in a method declaration only allows using await inside of the method. Thus, the async in this example is completely redundant and does not change the method in any way. It is called and executed like any other method.

EDIT: As it has caused some confusion on Reddit again with different wording and another example: Simply calling an async method does not spawn another thread:

private async void MyMethod()
{
   Debug.Print("B" + Thread.CurrentThread.ManagedThreadId);
   await Task.Delay(1000);
   Debug.Print("C" + Thread.CurrentThread.ManagedThreadId);
 }
 //...
 Debug.Print("A" + Thread.CurrentThread.ManagedThreadId);
 this.MyMethod();
 Debug.Print("D" + Thread.CurrentThread.ManagedThreadId);

All the methods will be called in the same Thread (assuming execution starts in an event handler) and the thread ids will be identical. However, the output will be ordered “A, B, D, C” as described later.

ThreadPool.QueueUserWorkItem

public void ThreadPool_QueueUserWorkItem()
{
   //Background Thread
   ThreadPool.QueueUserWorkItem(o => this.PrintInfo("ThreadPool"));
}

Using the ThreadPool class was the way to go before the TPL existed. Of course, it’s still possible and the passed method will definitely run in a background thread. However, it’s not possible to await the result, so by using it you won’t be able to benefit from the TPL or any of the new features.

Task.Factory.StartNew (outside of a task)

public void TaskFactory()
{
   //Background Thread * 
   //(* when not called from within a Task)
   Task.Factory.StartNew(() => this.PrintInfo("TaskFactory 0")); 
}

Task.Factory.StartNew was introduced in .NET 4.0 and uses TaskScheduler.Current to schedule tasks. The naming is quite unfortunate: TaskScheduler.Current is the default TaskScheduler and represents the “TaskScheduler associated with the currently executing task”. If there is no currently executing task (e.g. when called directly from an event handler), TaskScheduler.Current defaults to TaskScheduler.Default (which uses the ThreadPool to schedule tasks).

Don’t worry – this is a common source of confusion (Note my answer in the thread – at that time, I considered it an elegant solution to use reflection for “fixing” this. From today’s point of view I can only say that this would be a terrible idea; so don’t do that!).

Task.Factory.StartNew (within a task)

The previous snippet emphasized that the behavior of StartNew varies between calls inside and outside of a Task:

public void TaskScheduler_FromCurrentSynchronizationContext()
{
   //UI Thread this.PrintInfo("0");
   Task.Factory.StartNew(() => 
   {
      //UI Thread
      this.PrintInfo("1");
      //UI Thread
      Task.Factory.StartNew(() => this.PrintInfo("3"));
   }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}

In this example, we use StartNew within a task which was scheduled on a task scheduler created from the current SynchronizationContext (i.e. a task scheduler which always runs tasks in the UI thread). Note that the task created within this tasks inherits the task scheduler and executes on the UI thread, too. You can prevent this by passing TaskCreationOptions.HideScheduler.

As a consequence, you have to be very careful with StartNew as it is not always as obvious as in this example whether code currently runs inside a task (and thus, the task scheduler is inherited – unless the inheritance has been prevented, of course…). Because of this, I suggest to avoid using Task.Factory.StartNew without explicitly specifying a task scheduler.

Task.Run

Beginning with .NET 4.5, there is a much better alternative to Task.Factory.StartNew available: Task.Run which always schedules tasks to TaskScheduler.Default(which will execute them on a thread pool thread).

public void TaskRun()
{
   //Background thread
   Task.Run(() => this.PrintInfo("TaskRun 0"));
}

It doesn’t matter whether the method is called from within a task or not – the scheduler won’t depend on that. Thus, you should use Task.Run instead of Task.Factory.StartNew to schedule background work. In fact, when using async and await, Task.Run is the only method you will ever need to make your UI responsive most of the times.

Task.ContinueWith

public void ContinueWith()
{
   //Background thread *
   //(* when not called from within a Task)
   Task.Run(() => this.PrintInfo("0"))
      .ContinueWith(task => this.PrintInfo("1"));
}

Just like StartNew, Task.ContinueWith defaults to TaskScheduler.Current when selecting the task scheduler to use. This means that it depends on the currently executing task whether the code will continue on the UI thread or on a background thread. Of course, you can also override this behavior by explicitly passing a task scheduler:

public void ContinueWith2()
{
   //UI thread
   Task.Run(() => this.PrintInfo("0"))
      .ContinueWith(task => this.PrintInfo("1"), TaskScheduler.FromCurrentSynchronizationContext());
}

Nevertheless, using ContinueWith can lead to confusion very easily. Probably, you are better off without it (as there are alternatives).

async / await

await is like a fancy ContinueWith without its downsides. Everything behind the await gets executed after the awaited task has completed. However, await does not block the current thread. Instead, it uses ContinueWith to schedule the remainder of the method in the current synchronization context.

public async void UsingAwait()
{
   //UI Thread
   this.PrintInfo("0");
   //Background Thread
   await Task.Run(() => this.PrintInfo("1"));
   //UI Thread
   this.PrintInfo("2");
}

Here is what happens in detail:

  1. The method gets called and starts to execute
  2. The first call to PrintInfo happens from the UI thread
  3. A task is created and started (in a background thread). The method returns.
  4. After the background task has completed, the third PrintInfo will be executed in the synchronization context of the original method (also referred to as the “captured context”)

Note one important difference between ContinueWith and await: ContinueWith continues using TaskScheduler.Currentwhile await uses the current synchronization context. Those are two different concepts: When an event handler is executed, TaskScheduler.Current will return TaskScheduler.Default (causing execution in a background thread) while the synchronization context will ensure execution in the UI thread (it is set by default by the WPF and Windows Forms).

Using ConfigureAwait

It is possible to use await and continue in a background thread by using Task.ConfigureAwait to tell the runtime that it is free to use a background thread for continuation:

public async void ConfiguringAwaiter()
{
   this.PrintInfo("0");
   await Task.Delay(500).ConfigureAwait(false);
   //Background Thread
   this.PrintInfo("1");
   await Task.Delay(500);
   //Background Thread
   this.PrintInfo("2");
}

Note that the second await won’t use the synchronization context of the UI as it will be called from a background thread. There is a catch, however: There is no guarantee that execution will continue in a background thread. For very short tasks, it can also continue in the current thread:

public async void ConfiguringAwaiter2()
{
   //UI Thread
   this.PrintInfo("0");
   await Task.Delay(0).ConfigureAwait(false);
   //UI thread
   this.PrintInfo("1");
   await Task.Delay(0);
   //UI thread
   this.PrintInfo("2");
}

Because of this, I would not recommend to use ConfigureAwait as its behavior can be considered quite random. Instead, you should split the method and explicitly call Task.Run on the part you want to execute in a background thread.

Calling an async method without await

What’s the output of the following code?

public void CallingAsyncWrong()
{
   this.PrintInfo("0");
   this.Other();
   this.PrintInfo("3");
}
//...
private async Task Other()
{
   this.PrintInfo("1");
   await Task.Delay(500);
   this.PrintInfo("2");
}

If you guessed “0 1 2 3”, you are wrong. It’s actually “0 1 3 2”. Remember the initial asyc / await example? Once execution of an async method encounters an await, the method returns. Thus, the calling method will continue to execute before the called method has completed (in a logical sense). The compiler will actually raise a warning:

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.

Thus, you should never call an async method and not await it. This also means that you should not write async methods which return void as they can not be awaited (the only exception are methods which directly handle events / commands).

EDIT: Note that when you call the initial method not from an event handler, but from a thread pool thread (which has no single threaded synchronization context), the original method will still continue to execute at the first await in the called method. However, the called method will continue in another thread which might cause both methods to run in parallel from this point on.

Accidentally awaiting a nested Task

This method looks quite harmless. Clearly, it will only print “4” after Other (same code as in the previous example) has been executed completely, won’t it?

public async void NestedTask()
{
   this.PrintInfo("0");
   await Task.Factory.StartNew(() => this.Other());
   this.PrintInfo("4");
}

Unfortunately, that’s not true. As Other is async itself, it will return on the first await (returning a Task). Then, execution of the original method will continue. There are multiple possibilities to solve this issue. All have in common, that instead of awaiting the task returned by Task.Factory.StartNew (a Task<Task>), we need to await the task after awaiting that one (the inner / nested task).

await await Task.Factory.StartNew(() => this.Other());
await Task.Factory.StartNew(() => this.Other()).Unwrap();
await Task.Run(() => this.Other());

As you can see, the last option is simply using Task.Run instead of Task.Factory.StartNew. So if you are following the previous suggestion to prefer Task.Run over Task.Factory.StartNew, you are already off the hook for this one.

Waiting for the result of a Task

public void DontBlock()
{
   this.PrintInfo("1");

   var result = Task.Run(() =>
   {
      Thread.Sleep(5000);
      return 42;
   }).Result;

   this.PrintInfo("2");
}

Executing code asynchronously to the UI thread won’t bring you any benefit if you synchronously wait for the result in the UI thread. It will block. It is even possible to create a deadlock this way:

public void Deadlock()
{
   this.PrintInfo("1");
   var result = this.deadlock().Result;
   this.PrintInfo("2");
}

private async Task<string> deadlock()
{
   var a = await Task.Run(() => "A");
   var b = await Task.Run(() => "B");
   return a + b;
}

My recommendation for dealing with this is to stay clear of Task.Result and Task.Wait. You won’t need it if using async / await.

Conclusion

As a summary, here are some guidelines which you should follow when using the TPL and async / await:

  • If you are using await within a method called in the UI thread, it will continue to run in the UI thread (unless you specify otherwise). Use this to synchronize code back into the UI thread.
  • Don’t use Task.Factory.StartNew (unless specifying a TaskScheduler). Use Task.Run instead.
  • Avoid using Task.ContinueWith (unless specifying a TaskScheduler). Use async / await instead.
  • Avoid using ConfigureAwait as you can’t rely on its behavior.
  • Avoid writing async void methods unless they are called by an event handler or from a command
  • Always await the result of an async method. Never call an async method from a non-async one
  • Stay away from Task<Task<...>> by using Task.Run
  • Don’t use Task.Wait or access Task.Result – it might block your UI thread and might even lead to deadlocks. Use async / await instead.
  • Don’t use “old” ways of synchronizing with the UI thread such as Dispatcher.BeginInvoke. Final example: Use

await Task.Run(...) for everything which might block the UI and write everything else just like when developing synchronous code:

//Called by an event handler or command
public async void FinalExample()
{
   //Access the UI from the UI thread
   this.UIProperty = "Starting...";

   //Do some work (in background, without blocking the UI thread)
   await Task.Run(() => Thread.Sleep(5000));

   //Access the UI from the UI thread (via data binding)
   this.UIProperty = "Almost done!";

   //Do some work (in background, without blocking the UI thread)
   await Task.Run(() => Thread.Sleep(5000));

   //Access the UI from the UI thread
   this.UIProperty = "Done";
}

I hope you found today’s article helpful. Again, the source code can be found on GitHub.

Are you comfortable using Tasks and async / await? What are your key take-aways? Any doubts that prevent you from using it?
Whatever it is – let me know in the comments!

  • James

    Finally, a description of async/await that everyone can understand. Nicely done.

  • Pelhu

    Thank you very much

  • Nikolay Klimchuk

    How about Task.Run(async () => await DoWhateverAsync()); ?

  • zuckerthoben

    “Avoid using ConfigureAwait as you can’t rely on its behavior.”
    This recommendation is the exact opposite you find on a lot of sites. Example: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
    Could you further explain?

  • Jonathan Bakert

    The “calling async wrong” example is only wrong if you don’t intend that’s not your desired intent