async, await Examples
This page was last reviewed on Mar 1, 2023.
Dot Net Perls
Async. Think of a C# method that blocks and waits for input, like File.ReadAllText. If we call it directly, we have to wait for it to return before continuing.
Shows a taskShows a task
With async and await we call functions in an asynchronous way. We can call a method (like the File-reading method) async, and do other things while it works.
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.
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.
Detail A lambda expression is specified as the argument to Task.Run. This is an action delegate.
Detail 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.
Shows a task
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.
Shows a task
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).
Detail We call BackgroundMethod, then run some important logic in the for-loop every 100 ms.
Then We invoke Task.Start. Finally we await the task—both methods run at the same time.
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.
Detail Type "await" in front of Task.Run in BackgroundMethod. Note that the program still needs work.
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.
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.
Detail 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.
StreamReader ReadLine
Detail 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
A review. Programs have methods that do not immediately return. With the async and await keywords, we run methods in an asynchronous way. A slow call can occur with no program freeze.
Dot Net Perls is a collection of tested code examples. Pages are continually updated to stay current, with code correctness a top priority.
Sam Allen is passionate about computer languages. In the past, his work has been recommended by Apple and Microsoft and he has studied computers at a selective university in the United States.
This page was last updated on Mar 1, 2023 (edit).
© 2007-2024 Sam Allen.