C# async, await ExamplesUse async and await keywords, along with a Task. Call an async method with await.
dot net perls

Async. A river rushes along with no interruptions. Now imagine another river some distance away. It too flows with no interruptions. A bird's song can be heard.

Async functions. With async and await we call functions in an asynchronous way. Like the rivers these tasks can run with no interference. Many things happen at once.

First program. We use the async and await keywords to asynchronously run a method. The program begins a long-running method (HandleFileAsync).

Part 1 We create a Task instance by calling HandleFileAsync. The task starts, and (later in Main) we call Wait() for it to finish.

Part 2 This async method displays a status message, and does some long-running calculations. We use StreamReader and await ReadToEndAsync.

Part 3 We must be careful to call Wait() on the task we want to wait for. Sometimes the wrong Task can be waited on.

Info Please change the path to a large text file that exists on your computer. Any large text file will do.

C# program that uses async, await, Task
using System; using System.IO; using System.Threading.Tasks; class Program { public static void Main() { // Part 1: start the HandleFile method. Task<int> task = HandleFileAsync(); // Control returns here before HandleFileAsync returns. // ... Prompt the user. Console.WriteLine("Please wait patiently " + "while I do something important."); // Do something at the same time as the file is being read. string line = Console.ReadLine(); Console.WriteLine("You entered (asynchronous logic): " + line); // Part 3: wait for the HandleFile task to complete. // ... Display its results. task.Wait(); var x = task.Result; Console.WriteLine("Count: " + x); Console.WriteLine("[DONE]"); Console.ReadLine(); } static async Task<int> HandleFileAsync() { string file = @"C:\programs\enable1.txt"; // Part 2: status messages and long-running calculations. Console.WriteLine("HandleFile enter"); int count = 0; // Read in the specified file. // ... Use async StreamReader method. using (StreamReader reader = new StreamReader(file)) { string v = await reader.ReadToEndAsync(); // ... Process the file data somehow. count += v.Length; // ... A slow-running computation. // Dummy code. for (int i = 0; i < 10000; i++) { int x = v.GetHashCode(); if (x == 0) { count--; } } } Console.WriteLine("HandleFile exit"); return count; } }
HandleFile enter Please wait patiently while I do something important. test You entered (asynchronous logic): test HandleFile exit Count: 1916146 [DONE]

Task.Run example. This program runs a computation asynchronously on every line entered in the console. It keeps accepting lines even when computations are running.

Action A lambda expression is specified as the argument to Task.Run. This is an action delegate.


Allocate This method does a slow-running computation. But when run asynchronously, it does not cause the program to freeze.

Result Many user inputs can be handled while the computation is running. Each Allocate() call finishes at its own pace.

C# program that uses async computation
using System; using System.Threading.Tasks; class Program { static void Main() { while (true) { // Start computation. Example(); // Handle user input. string result = Console.ReadLine(); Console.WriteLine("You typed: " + result); } } static async void Example() { // This method runs asynchronously. int t = await Task.Run(() => Allocate()); Console.WriteLine("Compute: " + t); } static int Allocate() { // Compute total count of digits in strings. int size = 0; for (int z = 0; z < 100; z++) { for (int i = 0; i < 1000000; i++) { string value = i.ToString(); size += value.Length; } } return size; } }
hello You typed: hello good You typed: good day You typed: day Compute: 588889000 friend You typed: friend Compute: 588889000 Compute: 588889000 Compute: 588889000 Compute: 588889000

ContinueWith. In real-world programs, we often need to call one method after another—we create a method chain. The ContinueWith method on Task can be used to call methods sequentially.

Here From Main, we call the Run2Methods() method 10 times. It asynchronously calls GetSum and then MultiplyNegative1.

Tip MultiplyNegative1 is always called after GetSum. The ContinueWith method runs its code after the method in Task.Run.

C# program that shows ContinueWith method
using System; using System.Threading.Tasks; class Program { static void Main() { // Call async method 10 times. for (int i = 0; i < 10; i++) { Run2Methods(i); } // The calls are all asynchronous, so they can end at any time. Console.ReadLine(); } static async void Run2Methods(int count) { // Run a Task that calls a method, then calls another method with ContinueWith. int result = await Task.Run(() => GetSum(count)) .ContinueWith(task => MultiplyNegative1(task)); Console.WriteLine("Run2Methods result: " + result); } static int GetSum(int count) { // This method is called first, and returns an int. int sum = 0; for (int z = 0; z < count; z++) { sum += (int)Math.Pow(z, 2); } return sum; } static int MultiplyNegative1(Task<int> task) { // This method is called second, and returns a negative int. return task.Result * -1; } }
Run2Methods result: 0 Run2Methods result: -140 Run2Methods result: -204 Run2Methods result: -14 Run2Methods result: -91 Run2Methods result: -55 Run2Methods result: -30 Run2Methods result: 0 Run2Methods result: -5 Run2Methods result: -1

New Task, local method. Suppose we want to run a Task in the background. Some C# syntax features can help here—we can use a local method declaration (InnerMethod in the example).

Main We call BackgroundMethod, then run some important logic in the for-loop every 100 ms.

BackgroundMethod Contains a local function InnerMethod. We use the Task constructor to create a new Task.

Then We invoke Task.Start. Finally we await the task—both methods run at the same time.

C# program that uses async, local method, new Task
using System; using System.Threading.Tasks; class Program { static void Main() { // Run a Task in the background. BackgroundMethod(); // Run this loop in Main at the same time. for (int i = 0; i < 5; i++) { System.Threading.Thread.Sleep(100); Console.WriteLine("::Main::"); } } async static void BackgroundMethod() { // Use a local function. void InnerMethod() { while (true) { System.Threading.Thread.Sleep(150); Console.WriteLine("::Background::"); } } // Create a new Task and start it. // ... Call the local function. var task = new Task(() => InnerMethod()); task.Start(); await task; } }
::Main:: ::Background:: ::Main:: ::Background:: ::Main:: ::Main:: ::Background:: ::Main::

Warnings, await operator. If we use async without await, the C# compiler will warn us that we are not accomplishing anything. We must pair the 2 operators. Async methods must contain await.

To fix Type "await" in front of Task.Run in BackgroundMethod. Note that the program still needs work.

C# program that shows await operator warnings
using System; using System.Threading.Tasks; class Program { static void Main() { BackgroundMethod(); } async static void BackgroundMethod() { Task.Run(() => Console.WriteLine("X")); } }
warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the await operator to the result of the call. warning CS1998: 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 on a background thread.

Error, main method. The async keyword cannot be used on the Main method. So we will need to add a second method before using an await call.

C# program that causes compile-time error
using System; using System.Threading.Tasks; class Program { static async void Main() { } }
error CS4009: 'Program.Main()': an entry point cannot be marked with the 'async' modifier

Task. This is a class found in System.Threading.Tasks. A Task returns no value (it is void). A Task<int> returns an element of type int. This is a generic type.


Info We can call Task.Run, ContinueWith, Wait—we can even run Tasks without async and await.

CancellationToken We use a CancellationTokenSource with tokens to signal a task should exit early.

A pattern. Async and await are a code pattern—they allow methods to asynchronously run. They are a form of syntactic sugar. They make code that uses threads easier to read.

Note An async method will be run synchronously if it does not contain the await keyword.

Compiler. With async and await, the compiler helps with asynchronous code. We return a Task or void from an async method. Visual Studio reports errors on incorrect methods.

Types (StreamReader, HttpClient) contain "Async" methods. These should be called with the await keyword. And the await keyword must be used within an async method.


ReadLine, ReadLineAsync


Task.Start The first async method call can occur with the Task Start method. This is an instance method.

Also Event handlers can be used with async methods. This is not currently shown here.

ReadToEndAsync. When can ReadToEndAsync lead to a performance gain in a program? When we have high CPU usage with a file load, we can speed up completion time with async and await.

StreamReader ReadToEndAsync

Notes, terms. Asynchronous does not mean multithreaded code. By default, code written with async and await is single-threaded. But threaded code works well here.

And With the Task.Run method, we can make code that uses async and await multithreaded.

Important Other code can execute (even on the same thread) after an asynchronous task has started.

Some corrections. Thanks to Donnie Karns for pointing out that async code statements are not (by default) run on another thread. Thanks to Jim Boyer for correcting a "nested" task problem.

And Thanks to Andrew Dennison for reviewing the Allocate() method code which had an unnecessary statement.

A review. Programs are full of methods that do not immediately return. Sometimes an external slowdown, as from a network, is the cause, not processor usage.

With these keywords, we run methods in an asynchronous way. Threads are optional. This style of code is more responsive. A network access can occur with no program freeze.

© 2007-2021 sam allen. send bug reports to info@dotnetperls.com.