Why Asynchronous Programming?
Modern apps do a lot of I/O (disk, database, web APIs) and sometimes long-running work.
If you block the main thread while waiting, users see freezes, servers handle fewer requests, and battery drains faster.
Asynchronous programming lets your code start a task (e.g., read a file, call an API), then yield control while the OS/Runtime finishes the work. When the result is ready, your method resumes right where it left off. UI stays responsive; servers scale better.
Synchronous vs Asynchronous
| Aspect | Synchronous | Asynchronous |
|---|---|---|
| Execution | One step after another | Can yield and resume later |
| Main thread/UI | Blocks (freezes) during waits | Stays responsive |
| Server scalability | Fewer concurrent requests | Many more concurrent requests |
| When to use | Short CPU work | I/O waits, long tasks, concurrency |
Synchronous
string text = File.ReadAllText("data.txt"); // blocks until disk I/O completes
Console.WriteLine(text);
Asynchronous
string text = await File.ReadAllTextAsync("data.txt"); // yields while OS reads
Console.WriteLine(text);
async/await
- async marks a method that can await tasks and will be rewritten by the compiler into a state machine.
- await pauses the method until the awaited Task completes, then resumes.
- While paused, the current thread is free to do other things (handle UI events, process other requests).
async Task<int> CalculateSumAsync()
{
await Task.Delay(2000); // simulate async work (I/O-like wait)
return 42;
}
Task (for methods without a return value)
async Task DoWorkAsync()
{
await Task.Delay(1000);
Console.WriteLine("Work completed!");
}
Task (for methods with a return value)
async Task<int> CalculateAsync()
{
await Task.Delay(1000);
return 42;
}
CancellationToken
Cancellation lets you stop a running asynchronous operation before it finishes, in a cooperative and controlled way.
Example (Downloading with cancellation)
static async Task DownloadFileAsync(string url, string path, CancellationToken token)
{
using HttpClient client = new HttpClient();
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
using var stream = await response.Content.ReadAsStreamAsync(token);
using var fileStream = File.Create(path);
await stream.CopyToAsync(fileStream, 81920, token);
}
Parallel vs Asynchronous
Parallel: runs multiple tasks at the same time on multiple cores (CPU-bound). Asynchronous: waits efficiently for I/O or external operations (I/O-bound).
Example (Parallel CPU-bound)
Parallel.For(0, 10, i => DoHeavyWork(i));
Example (Async I/O-bound)
await DownloadFileAsync("https://example.com");
Combining Tasks
You can run multiple tasks concurrently using Task.WhenAll() or Task.WhenAny().
var task1 = DownloadFileAsync("file1");
var task2 = DownloadFileAsync("file2");
await Task.WhenAll(task1, task2);
Console.WriteLine("Both files downloaded");
- Task.WhenAll() runs all tasks concurrently and waits until all of them are completed.
- Task.WhenAny() waits until any one of the tasks has completed.
Common Pitfalls & Best Practices
Avoid async void
// Bad - hard to handle exceptions
async void ButtonClick() { }
// Good
async Task ButtonClickAsync() { }
Don’t block async code
// Bad - causes deadlocks
var result = GetDataAsync().Result;
// Good
var result = await GetDataAsync();
Exception Handling in Async Code
async Task ProcessWithRetryAsync()
{
try
{
await ProcessDataAsync();
}
catch (HttpRequestException ex) when (ex.Message.Contains("timeout"))
{
// Retry logic
await Task.Delay(1000);
await ProcessDataAsync();
}
}