On this page
Async/Await
Asynchronous programming prevents thread blocking during I/O operations — essential for web servers, desktop apps, and mobile backends.
async and await
async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
async Task Main()
{
Console.WriteLine("Fetching...");
string html = await FetchDataAsync("https://example.com");
Console.WriteLine($"Received {html.Length} characters");
}
Main().Wait();
Methods returning Task or Task<T> can be marked async. The await keyword suspends execution until the task completes without blocking the thread.
Task.Run for CPU-Bound Work
async Task<int> ComputeAsync()
{
return await Task.Run(() =>
{
int sum = 0;
for (int i = 0; i < 1_000_000; i++)
sum += i;
return sum;
});
}
Offload CPU-intensive work to the thread pool; keep I/O on async methods directly.
Parallel Tasks
async Task RunParallel()
{
Task<string> t1 = FetchAsync("https://api.example.com/a");
Task<string> t2 = FetchAsync("https://api.example.com/b");
string[] results = await Task.WhenAll(t1, t2);
Console.WriteLine($"Got {results.Length} responses");
}
async Task<string> FetchAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
Cancellation Tokens
async Task LongRunningAsync(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(100, token);
Console.WriteLine($"Step {i}");
}
}
async Task Demo()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
await LongRunningAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancelled");
}
}
Async Best Practices
- Avoid
async voidexcept for event handlers. - Use
ConfigureAwait(false)in library code. - Do not block on async code with
.Resultor.Wait()— it can cause deadlocks.
IAsyncEnumerable (C# 8+)
Stream async results:
async IAsyncEnumerable<int> GenerateNumbersAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
yield return i;
}
}
await foreach (var n in GenerateNumbersAsync())
Console.WriteLine(n);
ValueTask for Hot Paths
When async methods often complete synchronously, ValueTask avoids allocating a Task:
public ValueTask<User?> GetCachedUserAsync(int id)
{
if (_cache.TryGetValue(id, out var user))
return ValueTask.FromResult(user);
return new ValueTask<User?>(FetchFromDbAsync(id));
}
Exception Handling in Async Code
try
{
await FetchDataAsync(url);
}
catch (HttpRequestException ex)
{
logger.LogError(ex, "HTTP request failed");
}
Exceptions thrown in async methods propagate to the awaiting caller.
Deadlock Warning
// NEVER in ASP.NET or UI apps:
var result = FetchDataAsync(url).Result; // can deadlock
// ALWAYS:
var result = await FetchDataAsync(url);
Async in ASP.NET Core Controllers
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _repo.GetByIdAsync(id);
return user is null ? NotFound() : Ok(user);
}
Action methods should return Task<IActionResult> for I/O-bound operations.