08 Feb 2024




Intermediate

In C#, async and await are features introduced in C# 5.0 to simplify asynchronous programming. They are used to write asynchronous code in a more natural and readable way without blocking the execution of the calling thread.

Here's a brief explanation of async and await:

  1. async: async is used as a modifier for methods. It indicates that the method contains asynchronous operations. An async method can contain one or more await expressions.

  2. await: await is used to pause the execution of an async method until the awaited task completes. When the awaited task finishes, the execution of the method resumes. The await keyword can only be used inside methods marked with the async modifier.

Asynchronous programming:

In asynchronous programming with async and await in C#, the thread that executes the asynchronous method is not blocked while waiting for the result of the asynchronous operation. However, the continuation of the method after the await keyword does wait for the result.

When you use await, the method essentially splits into two parts:

  1. The part before the await keyword, which executes synchronously up to the await point.
  2. The part after the await keyword, which is a continuation that executes asynchronously when the awaited operation completes.

While the awaited operation is in progress, the thread that originally executed the method is freed up to do other work.

The await keyword itself doesn't block the execution of the method. Instead, it tells the compiler to automatically generate code to set up a callback to resume the method when the awaited operation completes.

So, while the thread executing the method is not waiting for the result, the continuation of the method (the code after the await statement) is waiting for the result to proceed. This allows for non-blocking asynchronous execution and efficient use of system resources.

Example 1

Below is an example of an async method that contains four await statements:

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        await ExampleAsync();
    }

    static async Task ExampleAsync()
    {
        // Simulate asynchronous operations with four await statements
        await Task.Delay(1000); // Wait for 1 second
        Console.WriteLine("First await completed.");

        await Task.Delay(2000); // Wait for 2 seconds
        Console.WriteLine("Second await completed.");

        await Task.Delay(3000); // Wait for 3 seconds
        Console.WriteLine("Third await completed.");

        await Task.Delay(4000); // Wait for 4 seconds
        Console.WriteLine("Fourth await completed.");
    }
}

In the provided program, the control flow moves through the asynchronous method ExampleAsync() and the await statements in the following manner:

  1. Main Method Entry:

    • The Main method is marked as async, allowing it to call asynchronous methods using await.
    • await ExampleAsync() is called, initiating the asynchronous execution of the ExampleAsync method.
  2. ExampleAsync Method Execution:

    • ExampleAsync method is entered.
    • The first await statement is encountered: await Task.Delay(1000);.
    • The Task.Delay(1000) method is invoked, which asynchronously delays execution by 1000 milliseconds (1 second).
    • The control returns to the caller (Main method) while the delay is in progress.
  3. Control Returns to Main:

    • As the first await is encountered, control returns to the Main method, and it awaits the completion of the ExampleAsync method.
  4. ExampleAsync Resumes Execution:

    • After 1 second, the first await in ExampleAsync completes, and the method resumes execution.
    • The console outputs "First await completed."
    • The second await statement is encountered: await Task.Delay(2000);.
    • Another asynchronous delay of 2000 milliseconds (2 seconds) is initiated.
    • Control returns to the caller (Main method) while the delay is in progress.
  5. Control Returns to Main Again:

    • As the second await is encountered, control returns to the Main method, and it awaits the completion of the ExampleAsync method.
  6. ExampleAsync Resumes Execution Again:

    • After 2 seconds, the second await in ExampleAsync completes, and the method resumes execution.
    • The console outputs "Second await completed."
    • The same pattern repeats for the third and fourth await statements, each time initiating a delay and returning control to the caller.
  7. Finalization:

    • After all four await statements have completed, the ExampleAsync method finishes executing.
    • The Main method completes awaiting the ExampleAsync method.
    • The program ends its execution.

This flow demonstrates how asynchronous programming in C# allows non-blocking execution, where the method can await the completion of asynchronous operations while allowing the thread to be freed up for other tasks.

Example 2

Here's a basic example to illustrate how async and await are used:

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        await DoSomethingAsync();
    }

    static async Task DoSomethingAsync()
    {
        // Simulate an asynchronous operation, such as making an HTTP request
        HttpClient client = new HttpClient();
        string url = "https://jsonplaceholder.typicode.com/posts/1";
        
        // Await the completion of the HTTP request
        HttpResponseMessage response = await client.GetAsync(url);

        // Once the response is received, read the content
        string content = await response.Content.ReadAsStringAsync();
        
        // Output the content
        Console.WriteLine(content);
    }
}

In the example provided, when you use await with an asynchronous operation like GetAsync and ReadAsStringAsync from HttpClient, the executing thread is not blocked while waiting for the operation to complete.

Here's what happens under the hood:

  1. When you call an asynchronous method marked with async, such as GetAsync, it initiates the operation (in this case, an HTTP request) asynchronously. This means that the method starts executing, but it doesn't block the thread while waiting for the operation to complete.

  2. When you use await with an asynchronous operation, like await client.GetAsync(url), the method is paused at that point, and the control is returned to the caller (in this case, Main method). This means that the thread executing the method is freed up to do other work.

  3. While the asynchronous operation (e.g., HTTP request) is in progress, the thread is free to do other tasks, like responding to user input, handling other requests, etc.

  4. When the awaited operation completes (in this case, when the HTTP request finishes), the method resumes execution after the await statement.

In summary, using async and await does not block the thread. Instead, it allows the thread to be freed up to perform other tasks while the asynchronous operation is in progress. This makes asynchronous programming in C# more efficient and scalable, especially in scenarios where you have many concurrent operations or need to maintain responsiveness in user interfaces.

c-sharp
asynchronous-programming
async
await