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<string> GetDataAsync()
{
string data = awaitFetchDataFromDatabaseAsync();
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 data = await 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<int> FetchDataAsync()
{
for (int i = 0; i < 10; 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<int> ComputeAsync()
{
// 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 result = await FetchDataAsync();
Dispatcher.Invoke(() =>
{
/* Update UI */
});
}
CancellationToken for Async Operations
Provide the ability to cancel long-running async operations using CancellationToken
.
public async Task<string> FetchDataAsync(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.