ZetCode

C# async/await

last modified March 13, 2021

C# async/await tutorial shows how to use async await keywords in C#.

With asynchronous programming, we can execute tasks concurrenly with the main program execution. The async and await keywords simplify asynchronous programming in C#. C# has asynchronous programming model built into the language.

Asynchronous programming is used for two kinds of tasks: I/O-bound tasks and CPU-boud tasks. Requesting data from a network, accessing a database, or reading and writing are IO-bound tasks. CPU-boud tasks are tasks that are computationally expensive, such as mathematical calculations or graphics processing.

The async modifier is used on a method, lambda expression or an anonymous method to create asynchronous methods. An async method runs synchronously until it reaches its first await operator, at which point the method is suspended while the awaited task is completed. In the meantime, the control returns to the caller of the method.

The await operator suspends the evaluation of the enclosing async method until the asynchronous operation completes. When the asynchronous operation finishes, the await operator returns the result of the operation, if any.

If the async method does not contain an await operator, the method executes synchronously.

In C#, a Task represents an asynchronous operation. It is analogous to a promise or future, used in other languages.

C# simple synchronous example

In the next example, we execute three methods synchronously.

Program.cs
using System;
using System.Diagnostics;
using System.Threading;

var sw = new Stopwatch();
sw.Start();

f1();
f2();
f3();

sw.Stop();

var elapsed = sw.ElapsedMilliseconds;
Console.WriteLine($"elapsed: {elapsed} ms");

void f1() 
{
    Console.WriteLine("f1 called");
    Thread.Sleep(4000);
}

void f2() 
{
    Console.WriteLine("f2 called");
    Thread.Sleep(7000);
}

void f3() 
{
    Console.WriteLine("f3 called");
    Thread.Sleep(2000);
}

With Thread.Sleep, we emulate some longer computations.

var sw = new Stopwatch();
sw.Start();

We measure the execution time of the methods with Stopwatch.

f1();
f2();
f3();

The methods are called consecutively.

$ dotnet run
f1 called
f2 called
f3 called
elapsed: 13034 ms

On our system, it took 13 s to execute the three functions.

C# simple asynchronous example

Now, the example is rewritten using async/await keywords.

Program.cs
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

var sw = new Stopwatch();
sw.Start();

Task.WaitAll(f1(), f2(), f3());

sw.Stop();

var elapsed = sw.ElapsedMilliseconds;
Console.WriteLine($"elapsed: {elapsed} ms");

async Task f1()
{
    await Task.Delay(4000);
    Console.WriteLine("f1 finished");
}

async Task f2()
{
    await Task.Delay(7000);
    Console.WriteLine("f2 finished");
}

async Task f3()
{
    await Task.Delay(2000);
    Console.WriteLine("f3 finished");
}

We measure the execution time of three asynchronous methods.

Task.WaitAll(f1(), f2(), f3());

The Task.WaitAll waits for all of the provided tasks to complete execution.

async Task f1()
{
    await Task.Delay(4000);
    Console.WriteLine("f1 finished");
}

The f1 method uses the async modifier and returns a Task. Inside the body of the method, we use the await operator on the Task.Delay.

$ dotnet run
f3 finished
f1 finished
f2 finished
elapsed: 7006 ms

Now the execution took 13 s. Also note that the order in which the tasks finished is different.

C# async Main method

When we are using the await operator inside the Main method, we have to mark it with the async modifier.

Program.cs
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace AsyncMain
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();

            Console.WriteLine("task 1");
            Task task1 = doWork();

            Console.WriteLine("task 2");
            Task task2 = doWork();

            Console.WriteLine("task 3");
            Task task3 = doWork();

            await Task.WhenAll(task1, task2, task3);

            Console.WriteLine("Tasks finished");

            sw.Stop();

            var elapsed = sw.ElapsedMilliseconds;
            Console.WriteLine($"elapsed: {elapsed} ms");
        }

        static async Task doWork()
        {
            await Task.Delay(1500);
        }
    }
}

Inside the Main method, we call the doWork three times.

$ dotnet run
task 1
task 2
task 3
Tasks finished
elapsed: 1550 ms

C# reading files asynchronously

C# has many built-in methods to read files asynchronously. For instance, the File.ReadAllTextAsync asynchronously opens a text file, reads all the text in the file, and then closes the file.

Program.cs
using System;
using System.IO;
using System.Diagnostics;
using System.Threading.Tasks;

var task1 = File.ReadAllTextAsync("data1.txt");
var task2 = File.ReadAllTextAsync("data2.txt");
var task3 = File.ReadAllTextAsync("data3.txt");
var task4 = File.ReadAllTextAsync("data4.txt");

Console.WriteLine("doing some work");

var tasks = new Task[] { task1, task2, task3, task4 };

Task.WaitAll(tasks);

var content1 = await task1;
var content2 = await task2;
var content3 = await task3;
var content4 = await task4;

Console.WriteLine(content1.TrimEnd());
Console.WriteLine(content2.TrimEnd());
Console.WriteLine(content3.TrimEnd());
Console.WriteLine(content4.TrimEnd());

In the example, we asynchronously read four files.

var content1 = await task1;

With await, we asynchronously unwrap the result of the task.

C# CPU-bound async tasks

In the next example, we work with CPU intensive computations.

Program.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

var tasks = new List<Task<int>>();

tasks.Add(Task.Run(() => DoWork1()));
tasks.Add(Task.Run(() => DoWork2()));

await Task.WhenAll(tasks);

Console.WriteLine(await tasks[0]);
Console.WriteLine(await tasks[1]);

async Task<int> DoWork1()
{
    var text = string.Empty;

    for (int i = 0; i < 100_000; i++)
    {
        text += "abc";
    }

    Console.WriteLine("concatenation finished");

    return await Task.FromResult(text.Length);
}

async Task<int> DoWork2()
{
    var text = string.Empty;

    for (int i = 0; i < 100_000; i++)
    {
        text = $"{text}abc";
    }

    Console.WriteLine("interpolation finished");

    return await Task.FromResult(text.Length);
}

We have two computationally intensive methods that concatenate strings hundred thousand times.

tasks.Add(Task.Run(() => DoWork1()));
tasks.Add(Task.Run(() => DoWork2()));

The Task.Run method queues the specified work to run on the ThreadPool and returns a task or Task<TResult> handle for that work.

return await Task.FromResult(text.Length);

We return the number of characters concatenated with text.Length. The Task.FromResult creates a Task<TResult> that's completed successfully with the specified result.

C# mulitple async requests

The HttpClient class is used for sending HTTP requests and receiving HTTP responses from the specified resource.

Program.cs
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

var urls = new string[] {"http://webcode.me", "http://example.com",
    "http://httpbin.org"};

using var client = new HttpClient();

var tasks = new List<Task<string>>();

foreach (var url in urls)
{
    tasks.Add(client.GetStringAsync(url));
}

Task.WaitAll(tasks.ToArray());

var data = new List<string> { await tasks[0], await tasks[1], await tasks[2] };

var rx = new Regex(@"<title>\s*(.+?)\s*</title>", RegexOptions.Compiled);

foreach (var content in data)
{
    var matches = rx.Matches(content);
    
    foreach (var match in matches)
    {
        Console.WriteLine(match);
    }
}

We download three web pages asynchronously and print their HTML title tags.

tasks.Add(client.GetStringAsync(url));

The GetStringAsync creates a new task. It sends a GET request to the specified url and returns the response body as a string in an asynchronous operation.

var rx = new Regex(@"<title>\s*(.+?)\s*</title>", RegexOptions.Compiled);

foreach (var content in data)
{
    var matches = rx.Matches(content);
    
    foreach (var match in matches)
    {
        Console.WriteLine(match);
    }
}

With the help of a regular expression, we get the title tags from the retrieved pages.

$ dotnet run
<title>My html page</title>
<title>Example Domain</title>
<title>httpbin.org</title>

In this tutorial, we have used async/await keywords to create asynchronous programs in C#.

Read C# tutorial or list all C# tutorials.