Chapter 22: Asynchronous Programming
Asynchronous programming lets your program do other things while waiting — and async/await is the easiest, cleanest, most beautiful way to write asynchronous code in C#.
I’m going to explain everything very slowly, step by step, with tons of real-life examples, clear analogies, and practical mini-projects — just like we’re sitting together in Hyderabad looking at the same screen. Let’s dive in! 🚀
1. What is Asynchronous Programming? (Super Simple Analogy)
Imagine you go to a restaurant:
- Synchronous (bad way): You order food → you stand at the counter and wait 10 minutes doing nothing → only after food arrives can you eat and leave. → Your whole day is blocked!
- Asynchronous (good way): You order food → while the chef cooks, you sit down, chat with friends, check your phone, or read a book → when the waiter says “food is ready”, you go eat. → You didn’t waste time!
async/await in C# is exactly that: You tell the program: “Go do this long task (like calling an API), but don’t wait here — go do other useful work. When the task is done, come back and continue from here.”
2. The Core Keywords: async & await
- async → marks a method as asynchronous (can use await inside)
- await → says: “I’m going to wait for this task to finish, but please don’t block the thread — let other code run meanwhile”
Rule: If a method uses await, it must be marked async. An async method usually returns Task or Task<T>.
3. Task & Task<T> – The Promise of Future Work
- Task → represents an asynchronous operation that does not return a value (like void, but async)
- Task<T> → represents an asynchronous operation that returns a value of type T
Example – Fake long-running task
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async Task DoLongWorkAsync() { Console.WriteLine("Starting long work..."); await Task.Delay(3000); // Simulate 3 seconds of waiting (real: API call, file read…) Console.WriteLine("Long work finished!"); } async Task<string> GetGreetingAsync() { await Task.Delay(2000); return "Hello from Hyderabad! 🌆"; } |
4. Calling Async Methods with await
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
async Task MainAsync() { Console.WriteLine("Before calling long work..."); await DoLongWorkAsync(); // Program waits here (but thread is FREE!) Console.WriteLine("After long work!"); string greeting = await GetGreetingAsync(); Console.WriteLine(greeting); } await MainAsync(); // In console apps, we call the async Main like this |
Important output order:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
Before calling long work... Starting long work... (wait 3 seconds) Long work finished! After long work! (wait 2 seconds) Hello from Hyderabad! 🌆 |
5. Real Example: Downloading Web Pages Asynchronously
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System.Net.Http; async Task DownloadWebPageAsync(string url) { using HttpClient client = new HttpClient(); Console.WriteLine($"Starting download from {url}..."); string content = await client.GetStringAsync(url); // Awaits without blocking! Console.WriteLine($"Downloaded {content.Length} characters from {url}"); } async Task MainAsync() { Console.WriteLine("Starting downloads...\n"); // Run multiple downloads concurrently! Task t1 = DownloadWebPageAsync("https://www.microsoft.com"); Task t2 = DownloadWebPageAsync("https://www.google.com"); Task t3 = DownloadWebPageAsync("https://dotnet.microsoft.com"); // Wait for all to finish await Task.WhenAll(t1, t2, t3); Console.WriteLine("\nAll downloads completed!"); } await MainAsync(); |
Result: All three websites are downloaded at the same time (concurrently) — super fast!
6. Parallel Programming – Running Many Tasks at Once
A. Parallel.ForEach – Simple Parallel Loops
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System.Threading.Tasks; List<string> websites = new List<string> { "https://www.microsoft.com", "https://www.google.com", "https://www.amazon.in", "https://www.flipkart.com", "https://www.youtube.com" }; Parallel.ForEach(websites, url => { using HttpClient client = new HttpClient(); string content = client.GetStringAsync(url).Result; // .Result = blocking (bad practice!) Console.WriteLine($"Downloaded {content.Length} chars from {url}"); }); |
Better (async + parallel):
|
0 1 2 3 4 5 6 7 8 9 10 11 |
await Parallel.ForEachAsync(websites, async (url, ct) => { using HttpClient client = new HttpClient(); string content = await client.GetStringAsync(url); Console.WriteLine($"Downloaded {content.Length} chars from {url}"); }); |
B. PLINQ – Parallel LINQ (Parallel Queries)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var largeNumbers = Enumerable.Range(1, 1000000); // Normal LINQ (single thread) var squares = largeNumbers .Where(n => n % 2 == 0) .Select(n => n * n) .ToList(); // PLINQ – uses multiple threads! var parallelSquares = largeNumbers .AsParallel() // Turn on parallelism .Where(n => n % 2 == 0) .Select(n => n * n) .ToList(); |
Warning: Parallel programming is awesome for CPU-heavy work, but not always faster for I/O (like web requests) — use async/await + Task.WhenAll for I/O.
7. Mini-Project: Async Weather Dashboard
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System.Net.Http; using System.Text.Json; class WeatherInfo { public string Name { get; set; } public MainInfo Main { get; set; } } class MainInfo { public double Temp { get; set; } } async Task<WeatherInfo> GetWeatherAsync(string city) { using HttpClient client = new HttpClient(); string url = $"https://api.openweathermap.org/data/2.5/weather?q={city}&appid=YOUR_API_KEY&units=metric"; string json = await client.GetStringAsync(url); return JsonSerializer.Deserialize<WeatherInfo>(json); } async Task MainAsync() { Console.WriteLine("Fetching weather for multiple cities...\n"); string[] cities = { "Hyderabad", "Delhi", "Mumbai", "Bangalore", "Chennai" }; var tasks = cities.Select(city => GetWeatherAsync(city)).ToArray(); WeatherInfo[] results = await Task.WhenAll(tasks); foreach (var weather in results) { Console.WriteLine($"{weather.Name}: {weather.Main.Temp}°C"); } } await MainAsync(); |
Note: For real OpenWeather API, you need a free API key → replace YOUR_API_KEY.
Summary – What We Learned Today
- async/await → write asynchronous code that looks synchronous but doesn’t block
- Task & Task<T> → represent async operations
- await → wait without blocking the thread
- Task.WhenAll → run many async tasks concurrently
- Parallel.ForEach / Parallel.ForEachAsync → parallel CPU work
- PLINQ (AsParallel()) → parallel LINQ queries
- Best for I/O → async/await + HttpClient
- Best for CPU-heavy → Parallel.ForEach or PLINQ
Your Homework (Super Practical!)
- Create a new console project called AsyncMaster
- Make an Async File Downloader:
- Take a list of 5–10 image URLs (e.g., from Unsplash)
- Download all images concurrently using HttpClient.GetByteArrayAsync()
- Save each to disk with File.WriteAllBytesAsync()
- Show progress (e.g., “Downloaded image 3/10”)
- Bonus: Add a timeout using CancellationToken
Next lesson: Modern C# Features (C# 9–14) – records, init-only properties, top-level statements, pattern matching, source generators…
You’re doing absolutely fantastic! 🎉 Any part confusing? Want more examples with Task.WhenAll, Parallel.ForEachAsync, or cancellation? Just tell me — I’m right here for you! 💙
