HomeSearch

C# async, await Examples (Task)

Use async and await keywords, along with a Task. Call an async method with await.
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.
With async and await in C# 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).

First: We create a Task instance by calling the HandleFileAsync method. The task starts, and we call Wait() for it to finish.

Messages: The method displays a status message after it starts. When it ends, the results are displayed.

HandleFileAsync: We use StreamReader and await ReadToEndAsync. Please change the path to a large text file that exists on your computer.

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

C# program that uses async, await, Task using System; using System.IO; using System.Threading.Tasks; class Program { public static void Main() { // 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); // 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"; 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; } } Output: initial HandleFile enter Please wait patiently while I do something important. Output: part 2 HandleFile enter Please wait patiently while I do something important. test You entered (asynchronous logic): test Output: final 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.

Action

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; } } Output 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; } } Output 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; } } Output ::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")); } } Output 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() { } } Output 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.Task

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.StreamReaderReadLine, ReadLineAsyncHttpClient

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.Threads

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.

Quote: The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread.

Asynchronous programming: Microsoft Docs
Concept. 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-2019 Sam Allen. Every person is special and unique. Send bug reports to info@dotnetperls.com.
Home
Dot Net Perls