Multi-Threading (3), async, await in C#

This is a series of articles about Multi-threading.

Introduction

The first article in this series, Multi-Threading (1), Concept: What, Why, written on 01/30/2023, introduced the multi-threading concept. The following articles should be the implementations of different approaches.

I used to plan to start from the original approach, the delegate implementation, then go to Asynchronous Programming Model (APM), Event-based Asynchronous Pattern (EAP), and Asynchronous Programming Model (APM). However, due to being busy with something else, I did not have much time to think about this topic; I even lost the logical clue of this series of articles. Occasionally, I saw one piece of notes, an interview question about multi-threading, very interesting, that leads to this article.

The async and await keywords, although they have been used in C# for more than 10 years, this implementation is a very new approach, quite different from the traditional one. Probably, just due to this reason, I may start from here, introducing the async and await and the newest implementation approach, without the impression from the old approaches; that might be a good approach because if someone jumps into the programming world, the C# world, they might not know the traditional implementation approaches. Let us start from here.

I will use the interview question as an example to discuss the async and await keywords and the multi-threading program in C# in detail.

The content of the article,

  • Introduction
  • Problem
  • "async" and "await" Key Words in C#
  • Thread Flow Analyses
  • Solution
  • Synchronous Process

The next article, Multi-Threading (3-1), async, multi-await, will give more example analysis from different angles.

Problem

We start with the interview question: how to get the expected output:

Hello World

By one of three workflows:

(1)

Workflow

(2)

(3)

"async" and "await" Key Words in C#

async and await are two new keywords introduced into C# 5.0 in 2012. At that time, CPU clock speed reached an upper limit imposed by physical laws. But chip makers started to deliver CPUs with several cores that can run tasks in parallel. Thus C# needed a way to ease asynchronous programming.

The "async" keyword marks a method asynchronous, meaning it can be run in the background while another code executes. When you mark a method as async, you can use the "await" keyword to indicate that the method should wait for the result of an asynchronous operation before continuing.

Code sample

public async Task<int> ExampleMethodAsync()
{
    var httpClient = new HttpClient();
    int exampleInt = (await httpClient.GetStringAsync("http://msdn.microsoft.com")).Length;
    ResultsTextBox.Text += "Preparing to finish ExampleMethodAsync.\n";
    // After the following return statement, any method that's awaiting
    // ExampleMethodAsync (in this case, StartButton_Click) can get the
    // integer result.
    return exampleInt;
}

async in C#

  • An asynchronous method is defined by using the async keyword and is referred to as an async method.
  • An async method is usually named with a suffix Async, such as in the .NET Base Class Library (BCL), but and is not mandatory. This suffix can be ignored in methods with common names like Button1_Click() or Main(). By the way, Main() is also marked with async in the code above; more on this point later.

await in C#

  • In the async method, once the keyword await is met for the first time
    • First, the remaining of task is actually executed by some random threads obtained from the runtime thread pool.
    • Secondly, the calling method to the async method is not blocking the main thread; it will return to the first calling code that is not marked as await.

The async and await keywords are the heart of async programming. By using those two keywords, one can create an asynchronous method almost as easily as creating a synchronous method, even without really understanding the runtime workflow.

Note

  • if the "await" keyword is not defined within an asynchronous method, there will be a compiling error. On the other hand, 
  • if there is no "await" keyword defined within an asynchronous method. i.e, a method defined by the keyword "async", the asynchronous method will become a synchronous method, and the compiler will warn in both the asynchronous method and the method that could await; see the screenshot below. The warning messages are shown as green under the line curve.

Highlighting Main, we get the warning message

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.

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.

Thread Flow Analyses

(1) ---

The output:

The code running flow according to the output:

As shown in the screenshot above:

once the keyword await is met for the first time in the main thread, 1, at Line 45,

  • First, the remaining task is actually executed by some random thread, 4, obtained from the runtime thread pool.
  • Secondly, the calling method to the async method is not blocking the main thread; 1, it will return to the first calling code that is not marked as await, Line 34

where the main thread,1, is in RED, while the async thread, 4, is in GREEN.

(2) ---

The output:

The code running flow according to the output:

As shown in the screenshot above:

once the keyword await is met for the first time in the main thread, 1, at Line 45,

  • First, the remaining task is actually executed by some random thread, 4, obtained from the runtime thread pool.
  • Secondly, the calling method to the async method is not blocking the main thread; 1, it will return to the first calling code that is not marked as await, Line 20

where the main thread,1, is in RED, while the async thread, 4, is in GREEN.

(3) ---

The output:

The code running flow according to the output:

As shown in the screenshot above:

once the keyword await is met for the first time in the main thread, 1, at Line 45,

  • First, the remaining task is actually executed by some random thread, 4, obtained from the runtime thread pool.
  • Secondly, the calling method to the async method is not blocking the main thread; 1, it will return to the first calling code that is not marked as await, nowhere

where the main thread,1, is in RED, while the async thread, 4, is in GREEN.

Solution: --- (3)

(1)

Output:

(2)

Output:

(3)

Output:

Synchronous Process

If we do not define any await within the async methods:

the program will be exactly the same as the synchronous process:

and the running flow is actually the same as the full async process:

Output:

References: