Asynchronous Programming
Synchronous vs Asynchronous
Synchronous code executes line by line in order. When it encounters a time-consuming operation, it blocks the current thread, making the program unresponsive. Asynchronous code releases the thread while waiting for time-consuming operations, allowing the program to continue doing other work.
Problems with synchronous blocking:
- The UI thread is occupied, causing the interface to freeze and become unresponsive
- Server threads are wasted waiting for I/O, reducing throughput
- Users experience longer wait times, resulting in poor experience
Advantages of asynchronous:
- I/O-intensive operations (network, file, database) do not block threads
- CPU-intensive operations can be offloaded to the thread pool, keeping the UI responsive
- Improved concurrency and throughput for applications
Example
using System;
using System.Threading;
Console.WriteLine("Sync start: " + DateTime.Now.ToString("HH:mm:ss"));
Thread.Sleep(2000);
Console.WriteLine("Sync end: " + DateTime.Now.ToString("HH:mm:ss"));
Sync start: 10:00:00
Sync end: 10:00:02
Task and Task<T>
Task represents an asynchronous operation that does not return a result. Task<T> represents an asynchronous operation that returns a result of type T. They are the core types of C# asynchronous programming.
| Type | Description | Example |
|---|---|---|
Task |
Async operation with no return value | Task.Delay(1000) |
Task<T> |
Async operation with a return value | Task.FromResult(42) |
Example
using System;
using System.Threading.Tasks;
Task noResultTask = Task.Delay(500);
Task<int> resultTask = Task.FromResult(42);
await noResultTask;
int value = await resultTask;
Console.WriteLine("Retrieved value: " + value);
Retrieved value: 42
async/await Syntax
The async modifier marks a method as asynchronous, and the await operator waits for an asynchronous operation to complete without blocking the thread. await can only be used inside an async method.
Syntax rules:
asyncmethods returnTask,Task<T>, orValueTask<T>awaitsuspends method execution, waits for the Task to complete, then continues- An
asyncmethod returns to the caller at the firstawait
Example
using System;
using System.Threading.Tasks;
async Task DoWorkAsync()
{
Console.WriteLine("Work start: " + DateTime.Now.ToString("HH:mm:ss.fff"));
await Task.Delay(1000);
Console.WriteLine("Work end: " + DateTime.Now.ToString("HH:mm:ss.fff"));
}
async Task<string> GetMessageAsync()
{
await Task.Delay(500);
return "Async message arrived";
}
await DoWorkAsync();
string msg = await GetMessageAsync();
Console.WriteLine(msg);
Work start: 10:00:00.000
Work end: 10:00:01.000
Async message arrived
Task.Run
Task.Run offloads CPU-intensive work to the thread pool for execution, avoiding blocking the current thread. Suitable for compute-intensive operations, not for I/O operations.
Example
using System;
using System.Threading.Tasks;
int ComputeHeavy()
{
int result = 0;
for (int i = 0; i < 100_000_000; i++)
{
result += i;
}
return result;
}
Console.WriteLine("Starting computation...");
int total = await Task.Run(() => ComputeHeavy());
Console.WriteLine("Computation result: " + total);
Starting computation...
Computation result: 4999999950000000
Task.WhenAll / WhenAny / Delay
| Method | Description |
|---|---|
Task.WhenAll |
Waits for all Tasks to complete |
Task.WhenAny |
Waits for any one Task to complete |
Task.Delay |
Asynchronously waits for the specified number of milliseconds, replacing Thread.Sleep |
Example
using System;
using System.Threading.Tasks;
async Task<string> FetchAsync(string site)
{
await Task.Delay(new Random().Next(500, 1500));
return site + " loaded";
}
Task<string> t1 = FetchAsync("SiteA");
Task<string> t2 = FetchAsync("SiteB");
Task<string> t3 = FetchAsync("SiteC");
string[] all = await Task.WhenAll(t1, t2, t3);
Console.WriteLine("--- WhenAll complete ---");
foreach (string s in all)
{
Console.WriteLine(s);
}
Task<string> first = await Task.WhenAny(t1, t2, t3);
Console.WriteLine("First completed: " + first.Result);
--- WhenAll complete ---
SiteA loaded
SiteB loaded
SiteC loaded
First completed: SiteA loaded
Async Method Naming Convention
Async method names should use the Async suffix. This is a .NET convention that makes it easy to distinguish between synchronous and asynchronous methods.
Rules:
- Methods returning
TaskorTask<T>should have theAsyncsuffix - e.g.,
Read()→ReadAsync(),GetUser()→GetUserAsync() - Event handlers are excluded (e.g.,
Button_Click)
Example
using System;
using System.Threading.Tasks;
async Task<string> ReadFileAsync(string path)
{
await Task.Delay(200);
return "Read: " + path;
}
async Task SaveDataAsync(string data)
{
await Task.Delay(100);
Console.WriteLine("Saved: " + data);
}
string content = await ReadFileAsync("/data.txt");
await SaveDataAsync(content);
Saved: Read: /data.txt
CancellationToken Cancellation Mechanism
CancellationToken is used for cooperative cancellation of asynchronous operations. The caller sends a cancellation signal through CancellationTokenSource, and the callee checks the token and exits early.
Usage steps:
- Create a
CancellationTokenSource - Pass
cts.Tokento the async method - The async method periodically checks
token.IsCancellationRequestedor passes it to cancellable APIs - Call
cts.Cancel()when cancellation is needed
Example
using System;
using System.Threading;
using System.Threading.Tasks;
async Task LongWorkAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Step " + (i + 1));
await Task.Delay(300, token);
}
Console.WriteLine("All done");
}
using var cts = new CancellationTokenSource();
cts.CancelAfter(1200);
try
{
await LongWorkAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation cancelled");
}
Step 1
Step 2
Step 3
Step 4
Operation cancelled
IAsyncEnumerable<T> Async Streams
C# 8 introduced IAsyncEnumerable<T>, supporting asynchronous generation and consumption of data streams. Use await foreach to iterate items asynchronously, suitable for scenarios like batch database reads and streaming network responses.
Example
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
async IAsyncEnumerable<int> GenerateNumbersAsync()
{
for (int i = 1; i <= 5; i++)
{
await Task.Delay(200);
yield return i;
}
}
await foreach (int num in GenerateNumbersAsync())
{
Console.WriteLine("Received: " + num);
}
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5
Common Async Pitfalls
⚠️ Avoid async void
async void cannot be awaited, exceptions cannot be caught, and it can cause application crashes. Only use it in event handlers.
⚠️ Avoid using .Result and .Wait()
Calling .Result or .Wait() in a UI or ASP.NET context can cause a deadlock: the async method waits for the caller to release the thread, while the caller waits for the async method to complete — both sides wait for each other.
⚠️ ConfigureAwait(false)
Library code should use ConfigureAwait(false) to avoid capturing the synchronization context, preventing deadlocks and improving performance.
Example
using System;
using System.Threading.Tasks;
async Task<string> BadMethodAsync()
{
await Task.Delay(100);
return "Done";
}
async Task SafeUsageAsync()
{
string result = await BadMethodAsync();
Console.WriteLine("Correct usage: " + result);
}
async Task LibraryMethodAsync()
{
string result = await BadMethodAsync().ConfigureAwait(false);
Console.WriteLine("Library usage: " + result);
}
await SafeUsageAsync();
await LibraryMethodAsync();
Correct usage: Done
Library usage: Done
❓ FAQ
📖 Summary
- Synchronous code blocks threads; asynchronous code releases threads while waiting, improving responsiveness and throughput
Taskrepresents an async operation with no return value;Task<T>represents an async operation with a return valueasyncmarks an async method;awaitwaits for an async operation without blockingTask.Runoffloads CPU-intensive work to the thread poolTask.WhenAllwaits for all to complete;Task.WhenAnywaits for the first to completeTask.Delayis the async replacement forThread.Sleep- Async methods should use the
Asyncsuffix CancellationTokenimplements cooperative cancellation, controlled viaCancellationTokenSourceIAsyncEnumerable<T>+await foreachenables async streams- Avoid
async void,.Result,.Wait(); useConfigureAwait(false)in library code
📝 Exercises
- Write an async method
DownloadAllAsyncthat usesTask.WhenAllto download 3 simulated data sources concurrently and returns an array of all results - Write an async countdown method that supports
CancellationToken, outputs remaining seconds each second, and prints "Countdown cancelled" when cancelled - Use
IAsyncEnumerable<int>to generate the first 20 Fibonacci numbers, iterate withawait foreach, and output each - Identify the problem in the following code and fix it:
async void Button_Click() { var data = GetDataAsync().Result; Console.WriteLine(data); }



