async and await Keywords
The async and await keywords make asynchronous code look and behave like synchronous code, dramatically simplifying async programming.
The Problem with Callbacks
Before async/await, async code used callbacks:
csharp1// Old callback style - "callback hell"2DownloadData(url, data =>3{4 ProcessData(data, processed =>5 {6 SaveData(processed, () =>7 {8 Console.WriteLine("Done!");9 });10 });11});
With async/await:
csharp1// Modern async/await - reads like sync code2var data = await DownloadDataAsync(url);3var processed = await ProcessDataAsync(data);4await SaveDataAsync(processed);5Console.WriteLine("Done!");
How async/await Works
The async Modifier
Marks a method as asynchronous:
csharp1async Task DoWorkAsync()2{3 // Method can now use await4 await Task.Delay(1000);5}
Key points:
asyncalone doesn't make code run asynchronously- It enables the use of
awaitin the method - The compiler transforms the method into a state machine
The await Keyword
Asynchronously waits for a task to complete:
csharp1async Task<string> GetDataAsync()2{3 // Thread is RELEASED here, not blocked4 string result = await httpClient.GetStringAsync("https://api.example.com");56 // Execution resumes here when data arrives7 return result;8}
What await does:
- Checks if the task is already complete
- If not, registers a continuation and returns (releases the thread)
- When task completes, resumes execution at the await point
Return Types
Task (no return value)
csharp1async Task PrintMessageAsync()2{3 await Task.Delay(1000);4 Console.WriteLine("Hello!");5}
Task (returns a value)
csharp1async Task<int> CalculateAsync()2{3 await Task.Delay(1000);4 return 42;5}67// Calling code8int result = await CalculateAsync();
async void (event handlers only!)
csharp1// ONLY use for event handlers2async void Button_Click(object sender, EventArgs e)3{4 await DoWorkAsync();5}67// NEVER use async void for regular methods!8// Exceptions can't be caught, can't be awaited
State Machine Transformation
The compiler transforms async methods into state machines:
csharp1// What you write:2async Task<int> GetValueAsync()3{4 Console.WriteLine("Before");5 await Task.Delay(1000);6 Console.WriteLine("After");7 return 42;8}910// Compiler generates something like:11Task<int> GetValueAsync()12{13 var stateMachine = new GetValueAsyncStateMachine();14 stateMachine.state = 0;15 stateMachine.MoveNext();16 return stateMachine.Task;17}
The state machine tracks:
- Current position in the method
- Local variables
- Where to resume after each await
Execution Flow
csharp1async Task ExampleAsync()2{3 Console.WriteLine("1. Start"); // Runs on original thread45 await Task.Delay(1000); // Thread released here67 Console.WriteLine("2. After delay"); // May run on different thread89 await Task.Delay(500); // Thread released again1011 Console.WriteLine("3. Done"); // May run on yet another thread12}
1Thread Timeline:2┌─────────┐ ┌─────────┐ ┌─────────┐3│ "Start" │ │ "After" │ │ "Done" │4│Thread 1 │ │Thread 2 │ │Thread 3 │5└────┬────┘ └────┬────┘ └────┬────┘6 │ │ │7 └──────────────────►└──────────────────►│8 await (released) await (released)
Multiple Awaits
Each await is a potential suspension point:
csharp1async Task ProcessAsync()2{3 var data = await FetchDataAsync(); // Pause 14 var processed = await ProcessAsync(data); // Pause 25 await SaveAsync(processed); // Pause 367 // Each await may resume on a different thread8}
Awaiting Multiple Tasks
csharp1// Sequential (slower)2var result1 = await GetData1Async();3var result2 = await GetData2Async();4// Total time: time1 + time256// Parallel (faster)7var task1 = GetData1Async();8var task2 = GetData2Async();9var result1 = await task1;10var result2 = await task2;11// Total time: max(time1, time2)1213// Or use WhenAll14var results = await Task.WhenAll(task1, task2);
Common Mistakes
Forgetting to await
csharp1// BUG: Task not awaited, runs in background2DoWorkAsync(); // No await!34// CORRECT:5await DoWorkAsync();
Using async void
csharp1// BAD: Can't catch exceptions, can't await2async void DoWork() { ... }34// GOOD: Return Task5async Task DoWorkAsync() { ... }
Blocking on async code
csharp1// BAD: Can cause deadlocks2var result = GetDataAsync().Result;34// GOOD: Use await5var result = await GetDataAsync();
Key Takeaways
asyncenablesawaitin a methodawaitreleases the thread while waiting- Return
TaskorTask<T>, avoidasync void - The compiler creates a state machine
- Execution may resume on different threads
- Don't block with
.Resultor.Wait()- useawait