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 handling all the things that comes with asynchronous and parallelism 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 are the advantages of the Await and Async keywords and what are the tradeoffs of this because all code is not a good candidate for parallelization. We saw a lot of "Make all async" recently in companies 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 separated thread. For example this code creates and launches a Task on a separated thread, even if nothing is executed:


Task.Run(() => { });

A Task distributes a work to a different thread. Be careful because unlike when your 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 it.

The TPL also provides a lot of features like the possibility to subscribe to when the work is completed (this is know as something called "continuation"):


Task<string>.Run(() => 
{
   return "Hello from a Task!";
});

In this code sample, we are asking a Task to execute some code on a separated 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 others features, let's play a little with the Task Parallel Library, I will use here a .NET 5 WPF application but I could have used any .NET application, the TPL is absolutely not a WPF specific technology.

Assume we have a app that contain 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 difficult to do 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, it results that during a little more that 2 seconds our application is entirely blocked because all this code is running on the UI thread. It means that you can't do something else on the app, you can't even move the application's window on the screen, and if the execution takes too much times so 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 do to avoid this bad user interface? A solution is to move this code execution on another thread that the UI's one.

Let's see what happens if we just pass our DoSomeWork() method as delegate's 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 that beside.

Now when we launch our application all is working 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 have 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 method DoSomeWork(), where our code is taking 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 all is working fine but the end of the method DoSomeWork 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 await to see what happens before continuing to execute the rest of the code.

It could 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 to be executed then we have a bug here. To solve it, we could await 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 two last lines, but what's the difference between this implementation and the one that just passed DoSomeWork method as delegate's parameter to a Task?

There is no difference so our application will encounter the same problem:

We now are in a WPF specific implementation but it exists a way to update the UI from another thread that 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, all 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 also could use some data returned by the Task, here an example of 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 are using 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 logic 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 can think all this information about the Tasks and the Taks Parallel Library is not relevant because we want to speak about async and await keywords, but it is important. You have to know all this stuff because we will see it later but async and await keywords does nothing else that using the Task Parallel Library for you to make asynchronous implementation simpler.

The last think you have to know about is how the Task Parallel Library is dealing with exceptions. You saw above some exceptions but it's only because I was executed the application in debug mode, in a release mode all theses exceptions would have been ignored so the application would have continue to work but not as excepected and no one would have noticed nothing. It's because you have to care about exceptions to be informed if an exception occured.

If we take one of our first implementation that 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 by 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 does nothing if you aren't executing it in debug mode. The exception only lives on the Task's thread so it will not interfer with the UI thread. It could be, but you have to explicitly implement it, 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 occured his value would be true, so I log a message then return. It works, but now, how to 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 result, you can use a Task.ContinueWith on the first one and check inside it if a 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 if any exception has occurred in the second Task.ContinueWith method so you have to add another one, and so one. It doesn't like very pretty and it can be quickly become a code that is hard to understand and to maintain. This is why the Task Parallel Libray is powerful but not that easy to work with.

The very last thing I want to tell about the TLP is that the Parallel.For method is not asynchonous. It will deport the code execution one other threads but not his own orchestration so if you have some big processings with a lots of iteration, you may encapsulate the Parralel.For in his 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, all you can do with them you can also do it without them, they are just providing some compilation magic for you.

It's important because it means that async and await are not avoiding some threading complexity, they are just hidding them to you (in something called a state-machine).

The main goal of async and await is to provide readability and maintenability. 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 more error-prone, the exceptions will no longer be ignored if you don't want to.

The async keyword is not working alone. If you are just using the async keywords what he does it that he introduces a lot of complexity by moving you entired method body into a generated class but it does not execute anything on a separated thread.

The await keyword allows to do some work somewhere else. It could be any processing like doing an http request, loading a file from the disk, ...

You can have an async method which not contains any await (which is bad but it works), but you can't have a not async method that implements an await.

You should always use the async and await keywords together. They have been designed to be use 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 it to complete before executing the rest of the method which needs the results.

I always could have write 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 into the DoSomeWorkAsync method, because the Task is awaited inside this method, and it's not all this method which is awaited. We can't await this implementation of DoSomeWorkAsync because this method hs a void return type, and it's not possible to wait 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" 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 his consumer to track for the result or if any exception has occurred during the process. By returning a Task, you don't have to change anything on your method but you can await it now, and if an exception occurs it will be return on the Task.

The things that can be a little tricky to understand is that you have to await the Task to get his result, that 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 execute the application with a debugger attached, the debugger will inform you that an exception has occurred but then the application will continue to work but the catch block won't be hit, it's because the Task is not awaited.

If we await the Task we will be noticed 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 it's not because a method is awaited that it's will be executed on a separated 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 some work on 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 in a better way by knowing what happens behind the scene, it will avoid to create some behaviors that seems unexpected (that will lead to bugs!).

To keep track of all the asynchronous code that have been executed and all the work that happening on different threads, the Await keyword is using something called a State Machine.

The State Machine

The State Machine is like a queue of asynchonous code blocks to execute, and the State Machine is adding itself between each block of code to keep track of what happenned and where it is in the continuation.

The State Machine also handles the results and potential errors of the different asynchronous block codes. Finaly the State Machine will ensure that the code after the await keyword will be executed.

So let's recap, what really happen 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 will be the compilated code 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 and await in this compilated code, it's because these 2 keywords does not really exists, they do not mean anything for the CLR they are just syntaxic sugar (a massive one) for the developers.

You can see the code that instanciates the State Machine, set his state then starts it in the GetMyName method, you also can notice that it returns the State Machine's Task, which allows to access to the result of his 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 get and set the result.

This compilated code is a little bit more difficult to apprehend that our original method that only has 3 lines in the method body, and the more your method will do the more the compilation will have to do for you.

Now there is a danger we have to speack about, the deadlocks.

Deadlocks

A deadlock can completely crash your application. It happens when you create a circular awaiting dependency between different threads. If a thread A awaits the thread B to complete, the thread B awaits the thread C to complete and the thread C awaits the thread A to complete, nothing will happens and your application is 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's thread to block until all this code is complete (Task.Wait). Here is the point: if the UI's thread is blocked and awaits for this task to complete, how the Dispatcher.Invoke will be able to complete? In fact it cannot, because we have a deadlock here: the Dispatcher.Invoke will awaits the UI thread to be available to interact with it, but the UI thread will awaits the Dispatcher.Invoke to get his 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 one of our previous example:


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 have now the same issue that just above: the UI thread is awaiting the DoSomeWorkAsync method to complete but it cannot because this method needs the UI thread to be available to interact with his components. It just cannot work. A solution to make an 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 have an exception because our method has some UI interactions while the UI thread is awaiting but it would work without the UI interactions).

Avoid unecessary parallelized code

Executing asynchronous code requires a lot of CPU because of all it needs to run the State Machines. A lot of asynchronous code could make your application look efficient because the UI will never be block but consuming a lot of CPU can result of very few efficient applications, that can drill the battery on a mobile for example. To make applications more efficients you need to use async and await only when you need to execute asynchronous code and not all the time, despite of what you can hear sometimes ("make all your code asynchronous!", "use async and await everywhere!").

Let's see an example of unecessary asynchronous code:


private async Task SomeAsyncMethod()
{
    await AnotherAsyncMethod();
}

private async Task AnotherAsyncMethod()
{
    await ThirdAsyncMethod();
}

private async Task ThirdAsyncMethod()
{
    await Task.Delay(500);
}

It could seems ridiculous here because the methods are all in the same place and are doing nothing but it's not uncommon to see some long runs of async method in enterprise legacies.

Let's take a look of what this code looks like when its 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 his own State Machine so the compilated code is complex and which require a lot of resources to run.

How could we improve this code? If we look at theses methods there is only one asynchronous code so we can remove all the others just by removing the await keyword and by returning the tasks. Here what's we got:


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 method ThirdAsyncMethod we also can removing his State Machine and returning 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 use the 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 compilated code is way more 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 sometines the good approach is to not introducing Await and Async in your code.

The last topic about Async and Await is the cancellation. How to cancel a Task? There is no method Cancel or Abort on a Task but your 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 asked.

By instanciating a CancellationTokenSource you can have a token (CancellationTokenSource.Token), which you can cancel like 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 asked 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 also can pass the Token as 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);
    }
}
July 14, 2022
  • .NET
  • Task Parallel Library
  • Async
  • Await