This is the first part of the series. Please follow this link to read the second part: Using async/await operators in C#, part 2.

This article reviews usage of the async/await operators, which became available in .NET Framework 4.5. They are part of the Task-based Asynchronous Pattern (TAP), which elegantly solves the problem of efficient thread synchronization. The main purpose of introducing async/await operators is to prevent the thread from being blocked by waiting for results of other threads. Such thread consumes system resources but doesn’t perform any useful action. It would be better to release the thread during waiting time and create a new one (or reuse a free one) as soon as possible to continue work.

Introduction

Thread synchronization problem occurs relatively infrequently because many systems support multithreading by design. As an example, let’s consider IIS server, where each incoming request is processed in a separate thread. That’s why you don’t need to create your own threads very often in order to perform many practical tasks. But multithreading becomes a nightmare for the developer when this problem arises. Implementation of this functionality is a relatively simple task, as opposed to debugging and testing. This is because defects in multithreaded applications may appear randomly and/or only in certain circumstances. For example, some multithreading defects can only be reproduced in specific runtime environment. New design patterns are being introduced in order to simplify development and debugging of multithreaded applications. One of them is Task-based Asynchronous Pattern (TAP). This paper considers application of the async/await operators, which came as a part of TAP. These operators became available in C# 5.0 since .NET Framework 4.5.

The first time I realized how valuable async/await are was during development of open source project named Burd’s Proxy Searcher [1]. The searcher allows you to look for working proxy servers in real time. Search engine uses public services such as http://google.com and sites with open proxy lists. Parallelism is quite important for fast proxy searching and checking if proxy works, as well as for the rational threads management. In fact, program stops working properly once it has about 500-1000 threads. And the number of new threads in my case could easily exceed one thousand items per second.

Indeed, Burd’s Proxy Searcher begins parallel checking if proxies are working once it finds a site with a list of proxies. For that purpose the following algorithm was used:

Algorithm #1

  1. Create new thread.
  2. Load content of http://google.com synchronously through a proxy.
  3. Proxy is considered non-working if an error occurs during content loading, in which case we proceed to step 5.
  4. Proxy is considered working if content analysis algorithm gives positive result, and non-working otherwise.
  5. Thread ends his job.

The program stopped working in case of processing too long proxy list because separate thread was created for each proxy. Plot of dependency between actions and number of created threads is given below[2]:

Сhart 1

Сhart 1

Analysis of algorithm №1 showed that the longest action is waiting for response from proxy server during request to http://google.com. In fact, 99% of its lifetime thread is just waiting or, in other words, does nothing. If we take into account the fact that some proxies don’t respond in about one minute, it becomes clear why program didn’t work well. It was just being killed by the number of the created threads. Thread pool usage resolved this problem, but search speeds became low. Thread management would become much better if we released thread for the waiting time and continued work in other threads (or in the same thread if possible) after receiving response from the proxy. TAP together with async/await operators plus I/O competition port threads technology solve this problem effectively.

The basic idea of async/await is that thread is not locked during waiting for the result of the asynchronous action. Let’s modify algorithm №1 as follows:

Algorithm #2

  1. Create new thread #1.
  2. Start downloading http://google.com/ through a proxy asynchronously in the thread #2 and end the thread #1.
  3. When content is downloaded, perform steps 4-6 in the stream #2.
  4. Proxy is considered non-working if error occurs during content loading, in which case proceed to step 6.
  5. Proxy is considered working if the selected algorithm of content analysis gives a positive result, and a non-working otherwise.
  6. Thread #2 ends.

Plot of dependency between actions and number of threads is given below:

Chart 2

Chart 2

This change does not lead to better thread management because the number of threads is never less than one (the moment when two threads exist is really short, so it doesn’t make much sense to take it into account). But this algorithm allows using technique named competition port threads, which provides control over thread into operating system on pending IO operations. This technique  is implemented by the HttpClient class, so let’s use it. Algorithm will look as follows:

Algorithm #3

  1. Create new thread #1.
  2. Start downloading http://google.com asynchronously through a proxy by HttpClient and end thread #1.
  3. When content finishes downloading, perform steps 4-6 in the HttpClient thread.
  4. Proxy is considered non-working if error occurs during content loading, in which case go to step 6.
  5. Proxy is considered working if selected algorithm of content analysis gives a positive result, and a non-working otherwise.
  6. HttpClient thread ends.

Plot of dependency between actions and number of threads is given below:

Chart 3

Chart 3

Now the longest action takes zero threads! Current version of Burd’s Proxy Searcher uses algorithm #3 which allows checking thousands of proxy servers simultaneously. It was only possible to check tens of proxies before the current algorithm implementation (when thread pool limitation was used). It means that application performance was increased by about two orders of magnitude, which is a very good indicator of the feasibility of using async/await (and of pointlessness of locking thread in a pending asynchronous operation). In addition, the usage of async/await reduces the number of deadlocks because there are no thread locks in case of waiting for the result of asynchronous operation. In this paper you will find description of these and other advantages of async/await operators which came with TAP.

Basic syntax

Let’s divide all asynchronous functions into three groups  by the type of the returned result.

1. Asynchronous function returns void.

2. Asynchronous function returns Task.

3. Asynchronous function returns Task <T>, where T is – the type of asynchronous operation result..

These three groups – or types, aw we will call them – of functions cover all requirements which an asynchronous function may have. Indeed, if, for example, you need to develop a quick function for logging some events in program, you can use the first type.

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            LogAsync("program started");
            //Another actions
        }

        private static void LogAsync(string information)
        {
            //Implementation
        }
    }
}

If you want to save some data asynchronously and wait for completion of this transaction, then the second type will be useful [3]:

using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Task task = SaveAsync();
            task.Wait();
        }

        private static Task SaveAsync()
        {
            return Task.Run(() =>
            {
                // Implementation
            });
        }
    }
}

The third type is useful for waiting for completion and getting result of some asynchronous operation:

using System;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            Task<string> task = DownloadAsync("http://google.com");
            Console.WriteLine(task.GetAwaiter().GetResult());
        }

        private static Task<string> DownloadAsync(string url)
        {
            return Task.Run(() => "content of url");
        }
    }
}

Keywords async/await are applicable only to asynchronous functions listed above. Indeed, error will be generated during compilation of the following code. This happens because function GetAsync is marked as async, but returns a string type [4]:

class Program
{
    static void Main(string[] args)
    {
    }

    private static async string GetAsync()
    {
        //...
    }
}

Operator async appears in this example for the first time [5]. It indicates that GetAsync function may use operator await inside. Note that the async operator does not make a function asynchronous!

Usage of the async operator is unreasonable without the await operator. Indeed, the following example will get a warning during compilation: this async method lacks ‘await’ operators and will run synchronously. Consider using the ‘await’ operator to await non-blocking API calls, or ‘await Task.Run(…)’ to do CPU-bound work in background.

class Program
{
    static void Main(string[] args)
    {
    }

    private static async void GetAsync()
    {
        //...
    }
}

This means that operators async and await always work in pair. MSDN provides the following definition of the await operator: the await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes. This definition is enough for a general understanding, but the operator has some features that are considered in detail below. It becomes clear from the definition that operand of await can be an object of type Task:

private static async void LogAsync(string error)
{
    Task task = Task.Run(() => { });
    await task;
    //The same, but shorter
    // await Task.Run(() => { });

    Console.WriteLine("extra actions");
}

Or of Task<T>:

private static async void LogAsync(string @event)
{
    Task<bool> task = Task.Run(() => true);
    await task;

    //The same, but shorter
    // await Task.Run(() => true);
     Console.WriteLine("extra actions");
}

Let’s explore features of the await operator for each of these three types of asynchronous functions.

First type of asynchronous functions

Let’s assume that we need to write an asynchronous function which stores information about some event in two places – a database and a file. According to the application architecture, these two actions should be performed sequentially. The following code which satisfies these requirements has been written:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            WriteThreadId(1);
            LogAsync("Program started");

            WriteThreadId(4);
            //Simulating program activity
            Thread.Sleep(3000);
            WriteThreadId(8);
        }

        private async static void LogAsync(string @event)
        {
            WriteThreadId(2);
            await Task.Run(() =>
            {
                WriteThreadId(3);
                Thread.Sleep(1000); // Simulating saving into database
                WriteThreadId(5);
            });

            WriteThreadId(6);
            Thread.Sleep(1000); // Simulating saving into file
            WriteThreadId(7);
        }

        private static void WriteThreadId(int checkpoint)
        {
            Console.WriteLine("Checkpoint: {0}, thread: {1}", checkpoint, Thread.CurrentThread.ManagedThreadId);
        }
    }
}

In this example, function main calls LogAsync and waits three seconds. LogAsync function creates a new thread that simulates saving event to the database. After that goes a simulation of saving event into a file. For better understanding of how the program works a set of checkpoints has been added. Each checkpoint displays its number and the identifier of the current thread.  Following results will be shown shortly after program starts:

Checkpoint: 1, thread: 1

Checkpoint: 2, thread: 1

Checkpoint: 3, thread: 3

Checkpoint: 4, thread: 1

Checkpoint: 5, thread: 3

Checkpoint: 6, thread: 3

Checkpoint: 7, thread: 3

Checkpoint: 8, thread: 1

As you may see, program operates with two threads: #1 (ID 1) and #2 (ID 3). Thread #1 executes at checkpoint 1 – it’s the main thread of application. Checkpoint 2 is located at the beginning of the asynchronous function LogAsync, but it executes in thread #1 as well. This proves that keyword async doesn’t make function asynchronous, but only allows you to use the await operator inside [6]. Actually creating a new thread makes the function asynchronous. Checkpoint 3 is executed shortly after thread #2 was created. At the same time thread #1 goes into standby mode at instruction Thread.Sleep (checkpoint 4). It should be noted that function LogAsync completed its work in the stream #1 at the await operator (because checkpoint 6 has not been executed yet)! Thus, our LogAsync function can be divided into two parts – a synchronous and an asynchronous. Synchronous part ends with the await operator, and asynchronous part begins after it [7]. This is true because checkpoints 6 and 7 are performed in the context of thread #2. Checkpoint 5 marks the end of saving into database. Code which is located after the await operator waits for completion of the created thread. Therefore, checkpoint 6 is executed after checkpoint 5. It executes in the same thread #2. In fact, checkpoints 6 and 7 reuse thread created by Task.Run call. Thread #2 quits at checkpoint 7, and after that thread #1 quits at checkpoint 8.

Use of keywords async/await allows to take a fresh look at the thread management inside function. Previously it was necessary to carefully read the function code in order to understand which thread is executed at a specific moment. Threads create function if use async/await – before await performed first thread and after – second one. It greatly facilitates the analysis of asynchrony. As a proof let’s use the same code example without checkpoints but with information about threads:

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            LogAsync("Program started");

            //Simulating program activity
            Thread.Sleep(3000);
        }

        private async static void LogAsync(string @event)
        {
            //Thread #1
            await Task.Run(() =>
            {
                //Thread #2
                Thread.Sleep(1000); // Simulating saving into database
            });

            //Thread #2
            Thread.Sleep(1000); // Simulating saving into file system
        }
    }
}

In this example both events of saving into the database and the file system are performed in one thread but their code is physically separated. Keywords async/await allow us to avoid creating additional threads when such separation is required. Absence of the locked threads during waiting for the asynchronous action reduces probability of getting a deadlock in your application.

Second type of asynchronous functions

Let’s add the ability to wait until asynchronous operation LogAsync completes. Example from the previous section will be changed as follows:

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            Task task = LogAsync("Program started");

            //Wait until logged
            task.Wait();
        }

        private async static Task LogAsync(string @event)
        {
            //Thread #1
            await Task.Run(() =>
            {
                //Thread #2
                Thread.Sleep(1000); // Simulate saving into database
            });

            //Thread #2
            Thread.Sleep(1000); // Simulate saving into file system
        }
    }
}

We will not consider thread distribution in LogAsync function again because it won’t be changed. The most interesting thing here is that LogAsync returns Task, but doesn’t contain a return operator. Ironically, such a program compiles successfully and runs correctly. The reason is that the await operator implicitly returns its operand from function LogAsync in thread #1. If you try to return Task explicitly in asynchronous functions of the second type, which use pattern Async / Await, you will get a compilation error: Since ‘LogAsync (string)’ is an async method that returns ‘Task’, a return keyword must not be followed by an object expression. Did you intend to return ‘Task <T>? Indeed, the following code does not compile because of that reason:

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            Task task = LogAsync("Program started");

            //Wait until logged
            task.Wait();
        }

        private async static Task LogAsync(string @event)
        {
            return Task.Run(() => Thread.Sleep(1000));
        }
    }
}

The result of asynchronous functions of second type is of type void, and therefore it’s possible to return from function with void value:

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            Task task = LogAsync(null);

            //Wait until logged
            task.Wait();
        }

        private async static Task LogAsync(string @event)
        {
            //Thread #1
            if (string.IsNullOrWhiteSpace(@event))
                return;

            //Thread #1
            await Task.Run(() => Thread.Sleep(1000));

            //Thread #2
            Thread.Sleep(1000); // Simulate saving into file system
        }
    }
}

Since the return statement has no operands, and LogAsync should return an instance of Task, a Task-stub is generated implicitly as a result of the function. This stub does nothing and is considered to be successfully completed. Such behavior is very convenient because there is no need to check for null variable ‘task’ in the Main function. In the same way you can exit from an asynchronous functions of the first type. But since LogAsync returns void, a Task-stub is not generated in that case.

Third type of asynchronous functions

Let’s add information about success or failure during saving event by LogAsync function. Then our example from previous sections will be changed as follows:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            Task<bool> task = LogAsync("Program started");

            //Wait until logged
            bool successful = task.GetAwaiter().GetResult();

            Console.WriteLine(successful ? "Event logged" : "Logging failed");
        }

        private async static Task<bool> LogAsync(string @event)
        {
            //Thread #1
            if (string.IsNullOrWhiteSpace(@event))
                return false;

            //Thread #1
            await Task.Run(() =>
            {
                //Thread #2
                Thread.Sleep(1000);
            });

            //Thread #2
            //Simulating result of async operation
            return true;
        }
    }
}

Just as in the previous example, thread distribution in function LogAsync remains unchanged, and the await operator implicitly returns its operand from function LogAsync in thread #1. However, now it returns not an object of type Task, but an object of type Task <bool>. The key difference with the previous example is that LogAsync should return the result of the asynchronous operation, in that case – a Boolean value, because type Task <bool> is specified as a result of function LogAsync. If return operator is located in the synchronous part of function LogAsync, then instance of Task <bool>-stub will be generated implicitly and returned by the function in thread #1. It is considered as successfully completed and provides the result of asynchronous operation. In our case the result is false.

So, the synchronous part of asynchronous functions of the first and second types can be interrupted by the return operator without an operand. Asynchronous function that returns a Task <T> can be interrupted by the return operator with an operand of type T. Asynchronous functions of the second and third type will generate and return a stub task in synchronous part of function, which simulates completion of the asynchronous operation.

Multiple await operators in one function

An asynchronous function which uses async/await operators may contain more than one await. Let’s analyze the distribution of threads in such a function.

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            Task task = LogAsync("Program started");

            //Thread #1
            task.Wait();
        }

        private async static Task LogAsync(string @event)
        {
            //Thread #1
            await Task.Run(() =>
            {
                //Thread #2
                Thread.Sleep(1000); // Simulate saving into database
            });

            //Thread #2
            await Task.Run(() =>
            {
                //Thread #3
                Thread.Sleep(1000); // Simulate saving into file
            });

            //Thread #3
        }
    }
}

It becomes clear from this distribution that each await operator exits from its thread where it was executed. The result of LogAsync function is formed only by the first await operator (second await is executed in another thread and cannot return result into thread #1).

It’s possible that the second await will not create thread #3. This happens when an asynchronous function invoked in the synchronous part is interrupted by the return operator:

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            Task task = LogAsync("Program started1");

            //Thread #1
            task.Wait();
        }

        private async static Task LogAsync(string @event)
        {
            //Thread #1
            await Task.Run(() =>
            {
                //Thread #2
                Thread.Sleep(1000); // Simulate saving into database
            });

            //Thread #2

            await SaveIntoFile(@event);

            //Thread #2
        }

        private async static Task SaveIntoFile(string @event)
        {
            if (@event == "Program started") //Ignore this event
            {
                //Thread #2
                return;
            }

            await Task.Run(() =>
            {
                //Thread #3
                Thread.Sleep(1000); // Simulate saving into file
            });
        }
    }

This behavior provides more details about how the instance of Task-stub is generated. Created instance of Task is directly related to the thread where operator await has been executed.

Getting result of the asynchronous function

Now we know that the await operator can return an instance of types Task or Task<T> into thread where it was executed. On the other hand, the await operator explicitly returns the result of asynchronous actions in asynchronous part of asynchronous functions of the third type. Consider this example:

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestProgram
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread #1
            try
            {
                Task<string> task = DownloadContentAsync(new Uri("http://google.com"));

                //Cannot use await here because main function cannot be marked as async
                Console.WriteLine(task.GetAwaiter().GetResult());
                task.Wait();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }

        private static async Task<string> DownloadContentAsync(Uri uri)
        {
            //Thread #1
            using (HttpClient client = new HttpClient())
            using (HttpResponseMessage response = await client.GetAsync(uri))
            {
                //Thread #2
                if (!response.IsSuccessStatusCode)
                {
                    throw new InvalidOperationException(string.Format("Cannot download content of {0}. status code: {1}", uri, response.StatusCode));
                }

                return await response.Content.ReadAsStringAsync(); //Return string value from async part of function
            }
        }
    }
}

First, the await operator stops execution of DownloadContentAsync in the main thread. Once client.GetAsync has completed its work, the await operator returns result of GetAsync into the asynchronous part of DownloadContentAsync function:

HttpResponseMessage response = await client.GetAsync(uri);

This is possible because function GetAsync in HttpClient class is an asynchronous function of the third type:

public Task<HttpResponseMessage> GetAsync(Uri requestUri)

Value of ReadAsStringAsync can be obtained in the same way and it can just be returned as a result of the asynchronous part of DownloadContentAsync.

return await response.Content.ReadAsStringAsync();

Unfortunately, await operator cannot be applied in the main function. That is why our example above uses GetAwaiter method in order to wait for completion of asynchronous function. But you should avoid usage of GetAwaiter in asynchronous functions because it locks the thread in waiting time. It means that almost whole application should use async/await operators. Applying them only to some of the methods leads to difficulties during implementation of new asynchronous features. There are some methods which allow converting old asynchronous methods to async/await style and they will be considered further in the series.

Synchronous code performed after await

Applications which are based on window interface often have a problem of synchronization between the asynchronous code and the window interface. This problem may be solved in different ways. For example, WPF solves it by using Dispatcher.Invoke method.


    <button name="button">Click me</button>

using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                Thread.Sleep(1000);
                Dispatcher.Invoke(() => button.Content = "Completed");
            });
        }
    }
}

We can simplify the syntax by using async/await here:

private async void Button_Click(object sender, RoutedEventArgs e)
 {
     //Thread #1
     await Task.Run(() =>
     {
         //Thread #2
         Thread.Sleep(1000);
     });

     //Thread #1
     button.Content = "Completed";
 }

This example demonstrates an interesting point: the code which executes after the await operator does it synchronously! It happens because WPF uses a special way of changing  default behavior of the await operator – it’s SynchronizationContext. You may notice that SynchronizationContext.Current was null in previous examples. But in the last one it’s not null in the main thread anymore. So, behavior of the await operator can be changed by SynchronizationContext. This example shows how to simplify syntax of asynchronous operations in WPF applications. We will discuss how to change this behavior further in this series of articles about async/await operators.

Conclusion

I received a great opportunity to develop new asynchronous features once I started using async/await in Burd’s Proxy Searcher application. Generally speaking, these features were present in the app even before introduction of async/await. Indeed, method ContinueWith of class Task could have been used in order to avoid thread lock. In that case code readability would suffer a lot because of the large number of nested operators. When you use async/await for the first time, you may think that it’s too inconvenient. But after writing several asynchronous methods you just fall in love with this pattern. This paper misses a few interesting aspect of using the pattern, such as exceptions handling in Async/Await, SynchronizationContext explanation, converting old-style asynchronous functions to new syntax and more examples from real applications.  Hopefully, all that will be considered in the second part of this article. Thank you for your attention!

Notes:

1. Source code of Burd’s Proxy Searcher can be obtained by running git command:

git clone git://git.code.sf.net/p/proxysearcher/code proxysearcher-code.

return to text

2. All plots in Introduction section represent ideal case, since threads may be reused or reserved and occasionally released during short period of time. Their purpose is to demonstrate the main idea of using async/await operators. return to text

3. We discussed earlier why it is unreasonable to use Wait to wait for result of asynchronous function. But its usage in the main function is justified because it is impossible to stop main thread of the program without stopping the program itself. This is why the async/await operators cannot be applied to the function main. return to text

4. Usage of async/await will generate compilation error if asynchronous function returns a type that is different from the void, Task or Task <T>: the return type of an async method must be void, Task or Task <T>. return to text

5. You must have Visual Studio version 2012 or higher and at least .NET Framework 4.5 in order to use the async keyword. If you try to compile a program that uses the async keyword with .NET framework version less than 4.5, you will get the following compilation error: Cannot find all types required by the ‘async’ modifier. Are you targeting the wrong framework version, or missing a reference to an assembly? return to text

6. Indeed, if you remove the async keyword in LogAsync function, you will get a compilation error: The ‘await’ operator can only be used within an async method. Consider marking this method with the ‘async’ modifier and changing its return type to ‘Task <System.Threading.Tasks.Task>. return to text

7. Actually, synchronous execution is possible after the await operator as well. Exceptions to that rule will be discussed further in this series. return to text

References:

1. MSDN: Task-based Asynchronous Pattern (TAP)

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert