Blog
Overview of Async and Await in C#
.NET introduced the Task Parallel Library in .NET 4 to write multithreaded and parallel code. The main goal of the TPL is to make developers more productive by simplifying the process of adding parallelism and concurrency to applications. By using the Task Parallel Library you can maximize the performance of your code while focusing on the work that your program is designed to accomplish, without having to handle all the things that come with asynchronous and parallel code, like the partitioning of the work, the state management or the continuation.
In this article we will cover what the TPL is and does, what the advantages of the Await and Async keywords are and what the tradeoffs are, because not all code is a good candidate for parallelization. We have seen a lot of "make everything async" in companies recently but it's often a mistake.
Like I just said, the Task Parallel Library allows you to ask for some code to run on a separate thread. For example, this code creates and launches a Task on a separate thread, even if nothing is executed:
Task.Run(() => { });
A Task distributes work to a different thread. Be careful because, unlike when you are handling the threads yourself, with the TPL the threads can be reused, so be very careful with objects that could be shared between executions like static data, or just don't use them.
The TPL also provides a lot of features like the possibility to subscribe to when the work is completed (this is known as "continuation"):
Task<string>.Run(() =>
{
return "Hello from a Task!";
});
In this code sample, we are asking a Task to execute some code on a separate thread and to return the result, which would be a string. Here the TPL will take care of the continuation for you (state management, ...), will run the code, wait until it's done, then return the result to you.
Before digging into other features, let's play a little with the Task Parallel Library. I will use a .NET 5 WPF application here but I could have used any .NET application, the TPL is absolutely not a WPF-specific technology.
Assume we have an app that contains a button and a text block, and when you click on it a method is called that writes something into the textblock and in the debug trace.
We have this XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Top" FontSize="30" Click="Button_Click">Do some work
And this code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWork();
}
private void DoSomeWork()
{
Debug.WriteLine("Just started the execution of the method DoSomeWork...");
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Thread.Sleep(2000);
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
Debug.WriteLine("Just ended the execution of the method DoSomeWork...");
}
}
As you can see, it's hard to make it any simpler. When you launch the app it does nothing, and when you click on the button you can see some traces in the debug console and in a textblock on the UI:

As you noticed, I added a thread pause to simulate some code execution here, and the result is that for a little more than 2 seconds our application is entirely blocked because all this code is running on the UI thread. It means that you can't do anything else in the app, you can't even move the application's window on the screen, and if the execution takes too long, Windows will inform you that this application may have encountered a problem and will ask you if you want to close it.
How can we avoid this bad user interface behavior? One solution is to move this code execution to a thread other than the UI one.
Let's see what happens if we just pass our DoSomeWork() method as the delegate parameter when creating a new Task. Our button's click method becomes:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(DoSomeWork);
}
Our DoSomeWork method is still the same as before.
Now when we launch our application everything works, but when we click on the button the app encounters an exception: The calling thread cannot access this object because a different thread owns it. This is explained by the fact that the UI thread is the main one while our method is now running on another thread that has no access to the UI, so when our method tries to update the user interface our application encounters a threading isolation exception.

What happens if we move the creation of our dedicated thread inside the DoSomeWork() method, where our code takes some time to execute (here our Thread.Sleep()):
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWork();
}
private void DoSomeWork()
{
Debug.WriteLine("Just started the execution of the method DoSomeWork...");
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task.Run(() => Thread.Sleep(2000));
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
Debug.WriteLine("Just ended the execution of the method DoSomeWork...");
}
Now everything works fine but the end of the DoSomeWork method is executed before the end of our task. It's completely normal because we don't await the Task's result, so the .NET Framework creates a new Task dedicated to our code, starts it and does not wait to see what happens before continuing to execute the rest of the code.
It may not be a problem if this piece of code is an atomic operation, but if it's not, and if the code behind needs it to be completed before running, then we have a bug here. To solve it, we could wait for the end of this Task before executing the end of our method. To do this, we can use the Task.ContinueWith method:
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWork();
}
private void DoSomeWork()
{
Debug.WriteLine("Just started the execution of the method DoSomeWork...");
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => Thread.Sleep(2000));
task.ContinueWith(resultTask => {
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
Debug.WriteLine("Just ended the execution of the method DoSomeWork...");
});
}
The Task.ContinueWith method creates a continuation that executes asynchronously when the target Task completes.
Now our code is waiting for our Task to complete before executing the last two lines, but what's the difference between this implementation and the one that just passed the DoSomeWork method as the delegate parameter to a Task?
There is no difference so our application will encounter the same problem:

We are now in a WPF-specific implementation, but there is a way to update the UI from a thread other than the main one that owns the UI: we can use what is called a Dispatcher:
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWork();
}
private void DoSomeWork()
{
Debug.WriteLine("Just started the execution of the method DoSomeWork...");
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => Thread.Sleep(2000));
task.ContinueWith(resultTask => {
Dispatcher.Invoke(() =>
{
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
Debug.WriteLine("Just ended the execution of the method DoSomeWork...");
});
});
}
If we launch our application now, everything will work as expected: when we click on the button, the first line appears, then the second one appears two seconds later without blocking our UI.

We could also use some data returned by the Task; here is an example of an implementation with a string result:
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWork();
}
private void DoSomeWork()
{
Debug.WriteLine("Just started the execution of the method DoSomeWork...");
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() => {
Thread.Sleep(2000);
return "Here some text from the Task!";
});
task.ContinueWith(resultTask => {
Dispatcher.Invoke(() =>
{
TracesTextBlock.Text += resultTask.Result;
Debug.WriteLine("Just ended the execution of the method DoSomeWork...");
});
});
}
Notice that moving the place where we use the Task's result can make a big difference in the execution. For example, if we use the result outside the ContinueWith then we will wait for the task to complete; it's logical because the task has to do what it has to do so we can have the result:
private void DoSomeWork()
{
Debug.WriteLine("Just started the execution of the method DoSomeWork...");
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() => {
Thread.Sleep(2000);
return "Here some text from the Task!";
});
string taskResult = task.Result;
task.ContinueWith(resultTask => {
Dispatcher.Invoke(() =>
{
TracesTextBlock.Text += taskResult;
Debug.WriteLine("Just ended the execution of the method DoSomeWork...");
});
});
}
You may think all this information about Tasks and the Task Parallel Library is not relevant because we want to talk about the async and await keywords, but it is important. You have to know all this stuff because we will see it later: the async and await keywords do nothing else than use the Task Parallel Library for you, to make asynchronous implementation simpler.
The last thing you have to know about is how the Task Parallel Library deals with exceptions. You saw some exceptions above but it's only because I was running the application in debug mode; in release mode all these exceptions would have been ignored, so the application would have continued to work but not as expected, and no one would have noticed anything. This is why you have to take care of exceptions, to be informed when an exception occurs.
If we take one of our first implementations, which was (I removed the debug lines to be shorter):
private void DoSomeWork()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => Thread.Sleep(2000));
task.ContinueWith(resultTask => {
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
});
}
And if we replace our thread pause with an exception, like this:
private void DoSomeWork()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => throw new Exception());
task.ContinueWith(resultTask => {
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
});
}
It will do nothing if you aren't running it in debug mode. The exception only lives on the Task's thread so it will not interfere with the UI thread. It could, but you have to implement it explicitly, like this:
private void DoSomeWork()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => throw new Exception());
task.ContinueWith(resultTask => {
if (task.IsFaulted)
{
Debug.WriteLine("An error occurred!");
return;
}
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
});
}
I added a test to check the IsFaulted property on the Task; if any exception has occurred its value would be true, so I log a message then return. It works, but now, how do we know if any exception occurs in the Task.ContinueWith method?
private void DoSomeWork()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => Thread.Sleep(2000));
task.ContinueWith(resultTask =>
{
if (task.IsFaulted)
{
Debug.WriteLine("An error occurred!");
}
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
});
}
As the Task.ContinueWith method also returns a task as its result, you can use a Task.ContinueWith on the first one and check inside it whether the previous Task is faulted:
private void DoSomeWork()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
Task task = Task.Run(() => Thread.Sleep(2000));
task.ContinueWith(resultTask =>
{
if (task.IsFaulted)
{
Debug.WriteLine("An error occurred!");
}
TracesTextBlock.Text += "Just ended the execution of the method DoSomeWork...\n";
}).ContinueWith(task =>
{
Dispatcher.Invoke(() =>
{
Debug.WriteLine("An error occurred in the first Method.ContinueWith!");
TracesTextBlock.Text += "An error occurred in the first Method.ContinueWith!";
});
});
}

But if you want to know whether any exception has occurred in the second Task.ContinueWith method, you have to add another one, and so on. It doesn't look very pretty and it can quickly become code that is hard to understand and to maintain. This is why the Task Parallel Library is powerful but not that easy to work with.
The very last thing I want to say about the TPL is that the Parallel.For method is not asynchronous. It will offload the code execution to other threads but not its own orchestration, so if you have some big processing with a lot of iterations, you may want to encapsulate the Parallel.For in its own Task:
private void DoSomeWork()
{
Task.Run(() =>
{
Parallel.For(0, 10, (i) =>
{
Debug.WriteLine("For execution " + i);
});
});
}

Let's talk about async and await now :)
Async & Await
Async and Await are just additional keywords; everything you can do with them you can also do without them, they just provide some compilation magic for you.
It's important because it means that async and await do not remove any threading complexity, they just hide it from you (in something called a state machine).
The main goal of async and await is to provide readability and maintainability. They allow you to write more fluent "top to bottom" code without nested calls that can become complex to work with.
Also, async and await are less error-prone: the exceptions will no longer be ignored, unless you want them to be.
The async keyword doesn't work alone. If you only use the async keyword, what it does is introduce a lot of complexity by moving your entire method body into a generated class, but it does not execute anything on a separate thread.
The await keyword allows you to do some work somewhere else. It could be any processing, like making an HTTP request, loading a file from the disk, ...
You can have an async method that doesn't contain any await (which is bad but it works), but you can't have a non-async method that implements an await.
You should always use the async and await keywords together. They have been designed to be used together.
Await:
- Marks a continuation
- Validates the success or failure of a Task
- Returns to the caller, with a reference to the ongoing method
Let's take our previous code example and modify it to make the DoSomeWorkAsync asynchronous using the Await and Async keywords.
Our previous code implementation was:
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWork();
}
private void DoSomeWork()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() => {
Thread.Sleep(2000);
return "Here some text from the Task!";
});
task.ContinueWith(resultTask => {
Dispatcher.Invoke(() =>
{
TracesTextBlock.Text += resultTask.Result;
});
});
}
And it becomes:
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWorkAsync();
}
private async void DoSomeWorkAsync()
{
TracesTextBlock.Text += "Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() => {
Thread.Sleep(2000);
return "Here some text from the Task!";
});
await task;
TracesTextBlock.Text += task.Result;
}
I just added an async keyword on the method and I await the task to wait for it to complete before executing the rest of the method, which needs the results.
I could also have written var taskResult = await task; to wait for the result and store it somewhere.

When we launch our application we can see that it works the same as before, but what will be the behavior if we write some text in the textblock outside of the DoSomeWorkAsync method?
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomeWorkAsync();
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}

As you can see, the text addition from the Button_Click method is hit before the last one in the DoSomeWorkAsync method, because the Task is awaited inside this method, and it's not the whole method that is awaited. We can't await this implementation of DoSomeWorkAsync because this method has a void return type, and it's not possible to await a void method because it returns nothing. To be able to await this method we have to make it return something; we can use any return type, the "default" one is Task (we also have to make the Button_Click method async):
private async void Button_Click(object sender, RoutedEventArgs e)
{
await DoSomeWorkAsync();
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}
private async Task DoSomeWorkAsync()
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() => {
Thread.Sleep(2000);
return DateTime.Now.ToString() + " || Here some text from the Task!";
});
await task;
TracesTextBlock.Text += task.Result;
}
This is why you should never use async void: it does not allow its consumer to track the result or to know whether any exception has occurred during the process. By returning a Task, you don't have to change anything in your method but you can await it now, and if an exception occurs it will be returned on the Task.
The thing that can be a little tricky to understand is that you have to await the Task to get its result, which can be an exception.
Assume this code:
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
DoSomeWorkAsync();
}
catch (Exception ex)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Exception! " + ex.Message + "\n";
}
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}
private async Task DoSomeWorkAsync()
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() =>
{
throw new Exception();
Thread.Sleep(2000);
return DateTime.Now.ToString() + " || Here some text from the Task!";
});
await task;
TracesTextBlock.Text += task.Result;
}
When you run the application with a debugger attached, the debugger will inform you that an exception has occurred, but then the application will continue to work and the catch block won't be hit; it's because the Task is not awaited.


If we await the Task we will be notified that an exception has occurred:
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
await DoSomeWorkAsync();
}
catch (Exception ex)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Exception! " + ex.Message + "\n";
}
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}

Another thing to know is that just because a method is awaited doesn't mean it will be executed on a separate thread.
Assume this code:
private async void Button_Click(object sender, RoutedEventArgs e)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Button_Click hit\n";
try
{
await DoSomeWorkAsync();
}
catch (Exception ex)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Exception! " + ex.Message + "\n";
}
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}
private async Task DoSomeWorkAsync()
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() =>
{
Thread.Sleep(2000);
return DateTime.Now.ToString() + " || Here some text from the Task!";
});
await task;
TracesTextBlock.Text += task.Result;
}
The only block that will be executed on a separate thread is the Task.Run block, the rest of the code is still executed on the UI thread. So if I add a Thread.Sleep inside the DoSomeWorkAsync outside the Task.Run it will block the UI:
private async void Button_Click(object sender, RoutedEventArgs e)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Button_Click hit\n";
try
{
await DoSomeWorkAsync();
}
catch (Exception ex)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Exception! " + ex.Message + "\n";
}
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}
private async Task DoSomeWorkAsync()
{
Thread.Sleep(4000);
TracesTextBlock.Text += DateTime.Now.ToString() + " || Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() =>
{
Thread.Sleep(2000);
return DateTime.Now.ToString() + " || Here some text from the Task!";
});
await task;
TracesTextBlock.Text += task.Result;
}
This behavior is normal, nothing offloads any work to a different thread except for the Task.Run in the DoSomeWorkAsync method.
Async and Await are very easy to use, but you will use them better by knowing what happens behind the scenes; it will avoid creating behaviors that seem unexpected (and that lead to bugs!).
To keep track of all the asynchronous code that has been executed and all the work that is happening on different threads, the Await keyword uses something called a State Machine.
The State Machine
The State Machine is like a queue of asynchronous code blocks to execute, and the State Machine adds itself between each block of code to keep track of what happened and where it is in the continuation.
The State Machine also handles the results and potential errors of the different asynchronous code blocks. Finally, the State Machine will ensure that the code after the await keyword will be executed.
So let's recap: what really happens when you add an await keyword?
- First it will generate a State Machine
- Then the body of the method that is awaited will be moved into this State Machine
Let's see what the compiled code will be for a basic async method:
using System.Diagnostics;
using System.Threading.Tasks;
public class C {
public async Task GetMyName()
{
string myName = "Adrien Torris";
await Task.Delay(2000);
Debug.WriteLine(myName);
}
}

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <GetMyName>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
awaiter = Task.Delay(2000).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
<>t__builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[AsyncStateMachine(typeof(<GetMyName>d__0))]
public Task GetMyName()
{
<GetMyName>d__0 stateMachine = default(<GetMyName>d__0);
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
}
The first thing to notice is that there is no async or await in this compiled code; it's because these 2 keywords don't really exist, they do not mean anything to the CLR, they are just syntactic sugar (a massive one) for developers.
You can see the code that instantiates the State Machine, sets its state then starts it in the GetMyName method; you can also notice that it returns the State Machine's Task, which gives access to the result of its execution: return stateMachine.<>t__builder.Task.
Our original code has been moved into the State Machine as I said before, in the MoveNext method. The method loops until the Task.Delay's GetAwaiter is done, then it gets and sets the result.
This compiled code is a little bit more difficult to grasp than our original method, which only has 3 lines in the method body, and the more your method does, the more the compilation will have to do for you.
Now there is a danger we have to talk about: deadlocks.
Deadlocks

A deadlock can completely crash your application. It happens when you create a circular waiting dependency between different threads. If thread A waits for thread B to complete, thread B waits for thread C to complete and thread C waits for thread A to complete, nothing will happen and your application has crashed.
Let's take a very basic example:
Task.Delay(1).ContinueWith((t) =>
{
Dispatcher.Invoke(() =>
{
});
}).Wait();
Can you see the deadlock here?
It's a little sneaky but it shows how easy it is to implement a deadlock situation. Here we ask to execute some work on a different thread (Task.Delay), then we want to continue on the UI thread (Task.ContinueWith -> Dispatcher.Invoke). It seems pretty good and we are coming back to the UI thread the right way, by using a Dispatcher, but here's the issue: at the bottom of all this we ask the UI thread to block until all this code is complete (Task.Wait). Here is the point: if the UI thread is blocked and waits for this task to complete, how will the Dispatcher.Invoke be able to complete? In fact it cannot, because we have a deadlock here: the Dispatcher.Invoke waits for the UI thread to be available to interact with it, but the UI thread waits for the Dispatcher.Invoke to get its job done to move on.
This code can be difficult to understand because it does nothing but we can create the same situation with our previous implementation.
Here is one of our previous examples:
private async void Button_Click(object sender, RoutedEventArgs e)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Button_Click hit\n";
try
{
await DoSomeWorkAsync();
}
catch (Exception ex)
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Exception! " + ex.Message + "\n";
}
TracesTextBlock.Text += DateTime.Now.ToString() + " || Here some text from the Button_Click\n";
}
private async Task DoSomeWorkAsync()
{
TracesTextBlock.Text += DateTime.Now.ToString() + " || Just started the execution of the method DoSomeWork...\n";
var task = Task.Run<string>(() =>
{
Thread.Sleep(2000);
return DateTime.Now.ToString() + " || Here some text from the Task! \n";
});
await task;
TracesTextBlock.Text += task.Result;
}
Now, for some reason, I need to make this code synchronous. I will use the Task.Wait method, so await DoSomeWorkAsync(); will become DoSomeWorkAsync().Wait();. But we now have the same issue as just above: the UI thread is waiting for the DoSomeWorkAsync method to complete but it cannot, because this method needs the UI thread to be available to interact with its components. It just cannot work. One solution to make asynchronous code synchronous is to wrap it in a dedicated Task; in our example it would be Task.Run(DoSomeWorkAsync).Wait(); (in our case we would get an exception because our method has some UI interactions while the UI thread is waiting, but it would work without the UI interactions).
Avoid unnecessary parallelized code
Executing asynchronous code requires a lot of CPU because of everything it needs to run the State Machines. A lot of asynchronous code could make your application look efficient because the UI will never be blocked, but consuming a lot of CPU can result in very inefficient applications, which can drain the battery on a mobile for example. To make applications more efficient you need to use async and await only when you need to execute asynchronous code and not all the time, despite what you can hear sometimes ("make all your code asynchronous!", "use async and await everywhere!").
Let's see an example of unnecessary asynchronous code:
private async Task SomeAsyncMethod()
{
await AnotherAsyncMethod();
}
private async Task AnotherAsyncMethod()
{
await ThirdAsyncMethod();
}
private async Task ThirdAsyncMethod()
{
await Task.Delay(500);
}
It may seem ridiculous here because the methods are all in the same place and are doing nothing, but it's not uncommon to see long chains of async methods in enterprise legacy code.
Let's take a look at what this code looks like when it compiles:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <SomeAsyncMethod>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public C <>4__this;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
C c = <>4__this;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
awaiter = c.AnotherAsyncMethod().GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
<>t__builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <AnotherAsyncMethod>d__1 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public C <>4__this;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
C c = <>4__this;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
awaiter = c.ThirdAsyncMethod().GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
<>t__builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <ThirdAsyncMethod>d__2 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
awaiter = Task.Delay(500).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
<>t__builder.SetStateMachine(stateMachine);
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[AsyncStateMachine(typeof(<SomeAsyncMethod>d__0))]
private Task SomeAsyncMethod()
{
<SomeAsyncMethod>d__0 stateMachine = default(<SomeAsyncMethod>d__0);
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
[AsyncStateMachine(typeof(<AnotherAsyncMethod>d__1))]
private Task AnotherAsyncMethod()
{
<AnotherAsyncMethod>d__1 stateMachine = default(<AnotherAsyncMethod>d__1);
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>4__this = this;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
[AsyncStateMachine(typeof(<ThirdAsyncMethod>d__2))]
private Task ThirdAsyncMethod()
{
<ThirdAsyncMethod>d__2 stateMachine = default(<ThirdAsyncMethod>d__2);
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
}

As you can see, each method has its own State Machine, so the compiled code is complex and requires a lot of resources to run.
How could we improve this code? If we look at these methods there is only one piece of asynchronous code, so we can remove all the others just by removing the await keyword and by returning the tasks. Here is what we get:
private async void Button_Click(object sender, RoutedEventArgs e)
{
await SomeAsyncMethod();
}
private Task SomeAsyncMethod()
{
return AnotherAsyncMethod();
}
private Task AnotherAsyncMethod()
{
return ThirdAsyncMethod();
}
private async Task ThirdAsyncMethod()
{
await Task.Delay(500);
}
For the ThirdAsyncMethod method we can also remove its State Machine and return a Task, like this:
private Task ThirdAsyncMethod()
{
return Task.Run(() => Task.Delay(500));
}
Notice that if this method had multiple independent code processes, like this:
private async Task ThirdAsyncMethod()
{
await Task.Delay(500);
await Task.Delay(2000);
}
We could have used Task.WhenAll to say that we want to return the Task when all the subprocesses are completed:
private Task ThirdAsyncMethod()
{
return Task.WhenAll(Task.Delay(500), Task.Delay(2000));
}
Now we see that the compiled code is much shorter, there is not all of the generated code we had before:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static Func<Task> <>9__2_0;
internal Task <ThirdAsyncMethod>b__2_0()
{
return Task.Delay(500);
}
}
private Task SomeAsyncMethod()
{
return AnotherAsyncMethod();
}
private Task AnotherAsyncMethod()
{
return ThirdAsyncMethod();
}
private Task ThirdAsyncMethod()
{
return Task.Run(<>c.<>9__2_0 ?? (<>c.<>9__2_0 = new Func<Task>(<>c.<>9.<ThirdAsyncMethod>b__2_0)));
}
}
Here we see that sometimes the right approach is to not introduce Await and Async in your code.
The last topic about Async and Await is cancellation. How do you cancel a Task? There is no Cancel or Abort method on a Task but you can provide a Token that you can cancel. Be careful because it's not automatic, you have to implement in your task the behavior you want when the cancellation has been requested.
By instantiating a CancellationTokenSource you can get a token (CancellationTokenSource.Token), which you can cancel like this: var cts = new CancellationTokenSource(); cts.Cancel();. To stop the process, you can throw an exception if the cancellation is requested, like this:
CancellationTokenSource cts = new CancellationTokenSource();
private async Task LongRunningOperationAsync()
{
while (true)
{
await Task.Delay(500);
cts.Token.ThrowIfCancellationRequested();
await Task.Delay(500);
}
}
Or you can check if the cancellation has been requested, then do whatever you want:
CancellationTokenSource cts = new CancellationTokenSource();
private async Task LongRunningOperationAsync()
{
while (true)
{
await Task.Delay(500);
if (cts.Token.IsCancellationRequested)
{
// Do some cancellation processing here.
}
await Task.Delay(500);
}
}
You can also pass the Token as an argument to your method:
private async void Button_Click(object sender, RoutedEventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
await LongRunningOperationAsync(cts.Token);
}
private async Task LongRunningOperationAsync(CancellationToken token)
{
while (true)
{
await Task.Delay(500);
token.ThrowIfCancellationRequested();
await Task.Delay(500);
}
}