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.
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.
We use the async
and await
keywords to asynchronously run a method. The program begins a long-running method (HandleFileAsync
).
HandleFileAsync
. The task starts, and (later in Main
) we call Wait()
for it to finish.async
method displays a status message, and does some long-running calculations. We use StreamReader
and await
ReadToEndAsync
.Wait()
on the task we want to wait for. Sometimes the wrong Task can be waited on.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
exampleThis program runs a computation asynchronously on every line entered in the console. It keeps accepting lines even when computations are running.
Task.Run
. This is an action delegate.Allocate()
call finishes at its own pace.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.
Main
, we call the Run2Methods()
method 10 times. It asynchronously calls GetSum
and then MultiplyNegative1
.MultiplyNegative1
is always called after GetSum
. The ContinueWith
method runs its code after the method in Task.Run
.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
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).
BackgroundMethod
, then run some important logic in the for
-loop every 100 ms.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::
await
operatorIf 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
.
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.
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
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.
Task.Run
, ContinueWith
, Wait—we can even run Tasks without async
and await
.CancellationTokenSource
with tokens to signal a task should exit early.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.
async
method will be run synchronously if it does not contain the await
keyword.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.
StreamReader
and HttpClient
contain "Async" methods. These should be called with the await
keyword. And the await
keyword must be used within an async
method.
async
method call can occur with the Task Start method. This is an instance method.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
.
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.