C# async/await
last modified July 5, 2023
C# async/await tutorial shows how to use async and await keywords in C#.
With asynchronous programming, we can execute tasks concurrently with the main
program execution. The async
and await
keywords
simplify asynchronous programming in C#. C# has asynchronous programming model
built into the language.
Concurrent 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 a concurrent operation.
C# simple synchronous example
In the next example, we execute three methods synchronously.
using System.Diagnostics; 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.
using System.Diagnostics; 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 7 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.
using System.Diagnostics; 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.
using System.Diagnostics; 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.
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.
using System.Text.RegularExpressions; var urls = new string[] { "http://webcode.me", "http://example.com", "http://httpbin.org", "https://ifconfig.me", "http://termbin.com", "https://github.com" }; var rx = new Regex(@"<title>\s*(.+?)\s*</title>", RegexOptions.Compiled); 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>(); foreach (var task in tasks) { data.Add(await task); } foreach (var content in data) { var matches = rx.Matches(content); foreach (var match in matches) { Console.WriteLine(match); } }
We download the given web pages asynchronously and print their HTML title tags.
tasks.Add(client.GetStringAsync(url));
The GetStringAsync
sends a GET request to the specified url and
returns the response body as a string in an asynchronous operation. It returns a
new task.
Task.WaitAll(tasks.ToArray());
The Task.WaitAll
waits for all of the provided tasks to complete
execution.
data.Add(await task);
The await
unwraps the result of the operation.
$ dotnet run <title>My html page</title> <title>Example Domain</title> <title>httpbin.org</title> <title>termbin.com - terminal pastebin</title> <title>GitHub: Where the world builds software · GitHub</title>
Source
Asynchronous programming with async and await
In this article we have used async/await keywords to create asynchronous programs in C#.
Author
List all C# tutorials.