Day 9: Asynchronous Programming
Welcome to Day 9. This is one of the most important concepts when moving from console apps to web servers: Asynchronous Programming.
The Problem (Blocking)
If you ask the database for 10,000 records, the CPU asks the hard drive/network for the data. The network tells the CPU “I’ll be a second.”
Synchronous code: The CPU literally sits there, blocked, twiddling its thumbs, refusing to serve any other web requests until that data returns. Asynchronous code: The CPU marks where it left off, and goes to serve other users. When the data arrives, it picks up exactly where it was.
Task, async, and await
In C#, a Task represents an asynchronous operation. Task<T> represents an operation that returns a value of type T.
To use them efficiently, C# has the async and await keywords.
// Making HTTP requests requires the System.Net.Http namespace
using System.Net.Http;
public class WeatherService
{
// 1. Mark the method as 'async'.
// 2. Return a 'Task<string>' instead of just 'string'.
public async Task<string> DownloadWeatherAsync()
{
HttpClient client = new HttpClient();
// 3. 'await' tells the CPU to go do other things while we wait
// for the API response. When it responds, resume execution here!
string data = await client.GetStringAsync("https://api.weather.gov/");
return data;
}
}
How to Call an Async Method
When you call an async method, you must await it. And if you await it, the calling method must also be marked async. It’s “async all the way down”.
In your Program.cs file (modern C# 9+ Top-Level statements support async implicitly!):
WeatherService svc = new WeatherService();
Console.WriteLine("Fetching weather...");
// We await the Task to unwrap the actual string inside it!
string w = await svc.DownloadWeatherAsync();
Console.WriteLine($"Got the data! Length: {w.Length}");
Task.WhenAll (Concurrency)
What if you need to make 3 separate HTTP requests? You shouldn’t wait for A to finish, then start B, then start C. Start them all at once!
var taskA = client.GetStringAsync("api.com/users");
var taskB = client.GetStringAsync("api.com/posts");
var taskC = client.GetStringAsync("api.com/comments");
// Starts them all concurrently, waits for the slowest one to finish!
await Task.WhenAll(taskA, taskB, taskC);
// Now all tasks are done! Extract data:
var users = taskA.Result;
var posts = taskB.Result;
Rules of Thumb
- Async all the way down: Once a method is async, every method that calls it should likely be async.
- Never use
Task.ResultorTask.Wait()on an incomplete task. This blocks the thread, causing catastrophic deadlocks in web applications. Always useawait! - Naming Convention: Append
Asyncto the end of your asynchronous method names (e.g.,GetUserAsync).
Challenge for Day 9
Create a simulated slow method using await Task.Delay(2000); to simulate a database call. Have your main program await it and measure how long it took using a Stopwatch class!
Tomorrow: Entering the Web — ASP.NET Core Architecture!