Understanding Async in C#

Understanding Async in C#

Introduction

Asynchronous programming is a critical aspect of modern software development, particularly in I/O-bound, database and network-bound operations.

In C#, async and await keywords are used to simplify the writing of asynchronous code, making it more readable and maintainable.

This article covers basic and a couple of advanced use cases of asynchronous programming in C#.

Basic Use Cases of Async in C#

Asynchronous Methods with async and await

Basic Async Method: Use the async modifier to define an asynchronous method, which typically returns a Task or Task<T>. Inside the method, use await to pause the execution until the awaited task completes.

public async Task<stringGetDataAsync() 

    string dataawaitFetchDataFromDatabaseAsync(); 
    
    return data; 
}

Calling Async Methods: When calling an async method, you typically await it. This means the calling method must also be async.

public async Task ProcessDataAsync() 

    string dataawait GetDataAsync(); 
    
    Console.WriteLine(data); 
}

Handling Exceptions in Async Methods

Exceptions in async methods can be caught using try-catch blocks.

public async Task HandleErrorsAsync() 
{ 
    try 
    { 
        await SomeOperationAsync(); 
    } 
    catch(Exception ex) 
    { 
        // Handle the exception 
    } 
}

Running Multiple Tasks Concurrently

Use Task.WhenAll to await multiple asynchronous operations.

public async Task GetAllDataAsync() 
{ 
    Task<string> task1 = FetchData1Async(); 
    Task<string> task2 = FetchData2Async(); 
    
    await Task.WhenAll(task1, task2); 
    
    string result1 = task1.Result
    string result2 = task2.Result
    
    // Process results 
}

Advanced Scenarios

Asynchronous Streams

C# 8.0 introduced asynchronous streams (IAsyncEnumerable<T>), useful for processing sequences of data asynchronously.

public async IAsyncEnumerable<intFetchDataAsync() 

    for (int i0; i10; i++) 
    {
        await Task.Delay(100); // Simulate asynchronous operation 
        yield return i; 
    } 


// Consuming an async stream 
public async Task ProcessDataAsync()

    await foreach (varitem in FetchDataAsync()) 
    { 
        Console.WriteLine(item); 
    } 
}

ValueTask for Performance Optimization

Use ValueTask or ValueTask<T> in high-performance scenarios where you need to avoid unnecessary allocations.

public async ValueTask<intComputeAsync() 

    // Some asynchronous operations 
    return 12;
}

Using Synchronisation Contexts

Be aware of the synchronisation context, especially in UI applications where you need to update the UI from an async method. C# ensures that the context is preserved, but sometimes you might need to explicitly switch to a different context.

public async Task UpdateUIAsync() 

    string resultawait FetchDataAsync(); 
    
    Dispatcher.Invoke(() => 

        /* Update UI */ 
    }); 
}

CancellationToken for Async Operations

Provide the ability to cancel long-running async operations using CancellationToken.

public async Task<stringFetchDataAsync(CancellationToken cancellationToken

    // Pass cancellationToken to all async calls 
    
    await Task.Delay(1000, cancellationToken);
    
    return "data"; 
}

Conclusion

Asynchronous programming in C# is a powerful feature that helps in writing non-blocking, responsive, and scalable applications.

The async and await keywords greatly simplify the process of writing and maintaining asynchronous code.

By understanding both the basic and advanced use cases, developers can effectively harness the power of async in C# for a wide range of applications, from simple I/O operations to complex, high-performance systems.

Stephen

Hi, my name is Stephen Finchett. I have been a software engineer for over 30 years and worked on complex, business critical, multi-user systems for all of my career. For the last 15 years, I have been concentrating on web based solutions using the Microsoft Stack including ASP.Net, C#, TypeScript, SQL Server and running everything at scale within Kubernetes.