CODINGTHOUGHTS

A blog about C#, Python, Azure and full stack development

Using async and await for Asynchronous Programming in C#

Asynchronous programming is a crucial aspect of modern software development, especially when dealing with I/O-bound operations like network requests, database access, or file system interactions. C# offers a powerful and intuitive way to handle asynchronous programming using the async and await keywords. This article will delve into the core concepts of async and await in C#, providing examples and best practices to help you harness the power of asynchrony in your applications. The keywords async and await in C# provide a modern way of implementatic multi-threading.

Summary

  • Asynchronous programming allows for non-blocking operations, ensuring responsive applications.
  • The core of async programming in C# revolves around the Task and Task<T> objects.
  • Use the async keyword to declare an asynchronous method and the await keyword to wait for a task to complete.
  • Differentiate between I/O-bound and CPU-bound tasks to use asynchronous programming effectively.
  • Always measure the execution of your asynchronous code to ensure optimal performance.

Understanding Asynchronous Programming in C#

Overview of the Asynchronous Model

C# provides a language-level asynchronous programming model that simplifies the process of writing non-blocking code. This model is centered around the Task and Task<T> objects, which represent asynchronous operations. These objects are complemented by the async and await keywords, making the asynchronous model in C# both powerful and easy to use. Learn more about this model from Microsoft’s official documentation.

For I/O-bound operations, such as reading from a file or making a network request, you would typically await an operation that returns a Task or Task<T> inside an async method. On the other hand, for CPU-bound operations like complex calculations, you would start the operation on a background thread using Task.Run and then await its completion.

The magic of asynchronous programming in C# lies in the await keyword. When you use await, the control is yielded back to the caller, allowing, for instance, a user interface to remain responsive.

I/O-bound vs. CPU-bound Work

It’s essential to distinguish between I/O-bound and CPU-bound tasks when working with asynchronous programming:

  • I/O-bound tasks: These are tasks that spend time waiting for something to complete, such as data from a database or a network request. For such tasks, use async and await without Task.Run.
  • CPU-bound tasks: These tasks involve intensive computations. If responsiveness is a concern, use async and await but offload the work to another thread with Task.Run.

Differentiating between these two types of tasks ensures that you’re using the right tools for the job, leading to more efficient and performant code.

Examples of Asynchronous Programming

I/O-bound Example: Download Data from a Web Service

Imagine you need to download data from a web service when a button is pressed, but you don’t want to block the UI thread. Here’s how you can achieve this using the System.Net.Http.HttpClient class:

private readonly HttpClient _httpClient = new HttpClient(); 
downloadButton.Clicked += async (o, e) => { 
  // This line will yield control to the UI as the request 
  // from the web service is happening. 
  var stringData = await _httpClient.GetStringAsync(URL);
  DoSomethingWithData(stringData); 
};

This code clearly expresses the intent of downloading data asynchronously without getting bogged down in the intricacies of Task objects.

CPU-bound Example: Perform a Calculation for a Game

Consider a scenario where you’re developing a game, and pressing a button inflicts damage on multiple enemies. Calculating the damage can be resource-intensive, and doing it on the UI thread might cause the game to lag. Here’s a solution:

private DamageResult CalculateDamageDone() 
{ 
  // Code for calculating damage 
} 

calculateButton.Clicked += async (o, e) => 
{ 
  var damageResult = await Task.Run(() => CalculateDamageDone());
 DisplayDamage(damageResult); 
};

This approach ensures that the game remains responsive while the damage calculation is being performed.

Under the Hood: How async and await Work

When you use async and await, the C# compiler transforms your code into a state machine. This state machine keeps track of things like pausing execution when an await is encountered and resuming execution when a background task completes. For those interested in the theoretical aspects, this is an implementation of the Promise Model of asynchrony.

Best Practices and Tips

  • Always suffix your asynchronous methods with “Async”.
  • Use async void only for event handlers.
  • Be cautious when using async lambdas in LINQ expressions.
  • Always await tasks in a non-blocking manner. Use await instead of Task.Wait or Task.Result.
  • Consider using ValueTask for performance-critical paths to reduce allocations.
  • Use ConfigureAwait(false) when you don’t need to marshal the continuation back to the original context.

Posted

in

by

Tags: