Skip to main content

Command Palette

Search for a command to run...

C# Asynchronous

Updated
6 min read
C# Asynchronous

Introduction

Hello all, today I back with another C# topic, and today I will share what i learned about C# Asynchronous. I hope with this article you will know more about C# Asynchronous and use it on your project.

What Is It?

Microsoft introduced asynchronous programming in .NET Framework 4.0 to improve the handling of long-running tasks, such as file I/O, network communication, or database operations, without blocking the main thread.

In traditional synchronous programming, operations are executed in a sequential order, meaning one operation must complete before the next one can begin. In contrast, asynchronous programming allows other tasks to run concurrently while waiting for a long-running operation to finish.

Why Implement It?

There are several pros and cons to consider before implement asynchronous in our project. Here are some of them:

Pros

  • Enhances application responsiveness during long-running operations.

  • Increases application scalability by allowing concurrent task handling.

  • Optimizes resource usage by freeing up threads while waiting for tasks.

Cons

  • May introduce deadlocks if not managed carefully.

  • Can lead to unexpected behavior due to context switching.

  • Can make debugging more challenging because of non-linear execution.

How It Works?

To understand how asynchronous work in the system, let's implement it first. In C#, a basic asynchronous operation requires three key components: async, Task, and await.

  1. Async

    This modifier is applied to methods to indicate they contain asynchronous operations and allows the method to use the await keyword.

  2. Await

    This keyword is used before calling an asynchronous method. It tells the compiler to pause the execution of the current method until the awaited task completes. Importantly, it doesn’t block the thread and lets the program continue executing other tasks while waiting for the asynchronous operation to finish.

  3. Task Or Task<T>

    These keywords represent an asynchronous operation. Task is used when the operation doesn’t return a value, and Task<T> is used when the operation returns a value of type T asynchronously.

using System;
using System.Threading.Tasks;

namespace Lncodes.Example.Async;

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

    private async Task LoadAssetAsync()
    {
        await Task.Delay(2000);
        Console.WriteLine($"Asset loaded.");
    }
}

In the code above, I have created an asynchronous method that will await 2 seconds before print "Asset loaded." on the console. After implementing an asynchronous method in the code, here's what will happen in the system:

  1. When an asynchronous method is called, it doesn't automatically run on a separate thread. Instead, it begins executing synchronously until it reaches the first await keyword.

  2. When the await keyword is encountered, the method is divided into two parts. The code before await runs immediately, while the code after await is delayed and executes later as a continuation (or callback) once the awaited operation completes (e.g., await Task.Delay(2000)).

  3. When the operation of await is running, the system will handle the threading differently. If the operation is I/O-bound (e.g., database query, file reading, or web request), the operation is sent to the OS kernel to allow it to communicate with the hardware (e.g., network card or disk controller). However, if the operation is CPU-bound (e.g., heavy calculations), it will be sent to another thread in the thread pool.

  4. Once the awaited operation completes, the Task Scheduler ensures the continuation executes in the correct context. If a SynchronizationContext is present (such as in a UI or ASP.NET application), it ensures the continuation is synchronized and runs in the appropriate context (e.g., back on the main thread) to prevent race conditions.

Console Application

In my console application, I use an asynchronous approach to create a game asset loader. This implementation ensures that assets like textures and audio files load without blocking the main thread. It also includes a cancellation feature, allowing the loading process to be stopped when necessary.

Below is a list of the classes I used to create this console application, along with a brief explanation of each class:

ClassDescription
GameAssetLoaderThis class is responsible for asynchronously loading game assets such as textures and audio files.
ProgramThis class is used to execute the game asset loading process asynchronously.

In the video above, the console application displays the asynchronous loading process of game assets, including textures and audio files, and shows how the process can be canceled midway.

The source code for this console application can be viewed and downloaded at Project Repository – Github

Additional Information

I have discovered some additional information about C# Asynchronous. If you have any additional information regarding C# Asynchronous that you'd like to share, please feel free to leave a comment.

Naming Guidelines

When implementing asynchronous, following specific naming guidelines can help ensure code readability and consistency. Here are some naming conventions recommended by Microsoft:

  • Do use 'Async' suffix for asynchronous method e.g. TimerAsync, PushDataAsync, etc.

Further Discoveries

Here are some further discoveries I have gathered from various sources that may provide a deeper understanding of C# Asynchronous:

  • Avoid using .Result or .Wait() to get results from a Task, as they can cause deadlocks. A better approach is to use await, which allows asynchronous code to run without blocking the current thread. Deadlocks are particularly problematic in UI applications and other scenarios where maintaining thread responsiveness is essential.

  • The ConfigureAwait(false) method can be used to improve the performance of asynchronous operations by preventing the continuation from capturing the original synchronization context. However, be cautious, as it may cause issues if your code depends on running on a specific thread, such as when updating the UI in desktop applications.

  • Avoid using async void, as it can lead to race conditions by allowing the calling code to continue without waiting for the asynchronous method to finish. Use async void only in cases where awaiting the method’s completion is unnecessary. In all other situations, prefer returning a Task or Task<T> to handle asynchronous operations more effectively.

  • Before .NET 4.5, Task.Factory.StartNew() was commonly used to create and start tasks with flexible configurations. However, starting with .NET 4.5, Task.Run() became the preferred option for simpler task creation and execution, as it runs tasks on the ThreadPool by default without needing additional configuration, unlike Task.Factory.StartNew().

  • A CancellationToken is used to signal and manage cancellation requests in asynchronous operations, allowing them to be interrupted or stopped before they complete.

  • A ValueTask can be used instead of a Task in asynchronous methods where the operation might complete synchronously or when the result can be produced without needing to await any operations.

Reference

  1. C# Asynchronous – Microsoft

  2. Synchronization Context – Microsoft

  3. Deep Dive C# Asynchronous – NDC Conferences

C#

Part 1 of 7

In this series, I’ll share the most important lessons and discoveries I’ve acquired about C#. From fundamental concepts to advanced features, discover how these lessons have sharpened my coding skills and how they can benefit to my projects.

Up next

C# Generic

Introduction Hello all, today I back with another C# topic, and today I will share what i learned about C# Generic. I hope with this article you will know more about C# Generic and use it on your project. What It Is? Introduced in C# 2.0, generic pro...