Async/Await

Course 2 · Ch 4
Async/Await
C# 5, 2012 — five years before the JavaScript syntax it directly inspired

java2-5 covered concurrency the manual way — raw Thread, synchronized, thread pools. C# has a genuinely different, much higher-level answer for a large slice of that same territory, and it's the actual origin of syntax you may already recognize from JavaScript.

async/await Basics

async Task<string> FetchDataAsync() { await Task.Delay(1000); // suspends here — doesn't block the thread return "data"; } string result = await FetchDataAsync();

async marks a method as containing suspension points; await marks each one. C# 5 (2012) introduced this exact syntax pattern as a real language feature — genuinely the origin, not a parallel invention: JavaScript's own async/await (ES2017, 2017) was explicitly modeled on C#'s design, five years later.

Task and Task<T>

Task (no result) and Task<T> (a future result) are what an async method returns — conceptually comparable to a JS Promise. This is a genuinely higher-level tool than anything java2-5 covered: Thread/Runnable/synchronized/java.util.concurrent are all real, manual thread-management primitives; Task-based async/await is a language feature built on top of that kind of machinery, not a replacement requiring the same manual bookkeeping.

What await Actually Does

await suspends the async method without blocking the calling thread — the thread is released to do other work, and the method resumes later via a continuation once the awaited Task completes. This is genuinely comparable to JavaScript's own event-loop-based async model, not java2-5's traditional blocking-thread model, where a thread sits idle (or is explicitly managed) while waiting.

async void — Fire-and-Forget, A Real Gotcha

async void OnButtonClick(object sender, EventArgs e) { // only legal use case: event handlers await DoWorkAsync(); } // async Task, not async void, for everything else — exceptions here CAN be caught normally async Task ProcessAsync() { await DoWorkAsync(); }

async void exists specifically for event handlers, which can't return a Task due to the delegate signature they must match. A genuine, well-documented pitfall: an exception thrown inside an async void method can't be caught by its caller the normal way — it crashes the process instead of propagating as a catchable exception. Prefer async Task even for methods that logically return nothing.

Contrasted with java2-5's Concurrency Tools

java2-5's Thread/synchronized model is about managing real OS threads directly — genuinely necessary for CPU-bound parallel work. C#'s async/await is about something different: not blocking a thread while waiting on I/O (network calls, disk, database queries) — for pure I/O-bound work, no extra thread is used at all while awaiting. For genuine CPU-bound parallel work, C# has Task.Run(), which does use the thread pool — that's the territory actually comparable to java2-5's own material.

AspectJava (java2-5)C#JavaScript
Modelmanual Thread/synchronizedasync/await, language-levelasync/await, language-level
Blocks the calling thread while waitingyes, by defaultno — thread released during awaitno — single-threaded event loop
Syntax arrivaln/a — thread APIs since Java 1.0C# 5 (2012) — the originES2017 (2017) — modeled on C#'s
Task.Run() for CPU-bound work; plain async/await for I/O-bound work
Reach for Task.Run() specifically when genuine parallel CPU work is needed — that's the territory closest to java2-5's own thread-pool material. Plain async/await over I/O (network, disk, database) needs no extra thread at all while suspended.
Avoid async void outside of event handlers
An unhandled exception inside an async void method crashes the process rather than being catchable by the caller — a genuinely dangerous gotcha for code that looks completely ordinary at the call site. Use async Task everywhere except the one legitimate case: an event handler whose delegate signature requires void.

Coding Challenges

Challenge 1

Write an async Task<int> method that awaits Task.Delay(500) and then returns 42, and call it with await from an async Main method, printing the result.

📄 View solution
Challenge 2

Write two async Task methods that each await a short delay and print a message, then call both without awaiting immediately (storing the returned Tasks), and finally await both together, demonstrating they ran concurrently rather than one after the other.

📄 View solution
Challenge 3

Write an async void method that throws an exception, call it, and show the caller cannot catch the exception with a normal try/catch around the call. Then rewrite it as async Task and show the exception CAN now be caught normally.

📄 View solution

Chapter 4 Quick Reference

  • async/await is real language syntax since C# 5 (2012) — the direct origin of JavaScript's own ES2017 async/await
  • Task/Task<T> is what an async method returns — a much higher-level tool than java2-5's manual Thread/synchronized primitives
  • await suspends without blocking the calling thread — the thread is released, resumed later via a continuation, closer to JS's event loop than Java's blocking-thread model
  • async void is only for event handlers — exceptions inside it crash the process rather than being catchable; use async Task everywhere else
  • Task.Run() is the genuine CPU-bound-parallelism tool, comparable to java2-5's own territory — plain async/await is for I/O-bound waiting, no extra thread needed
  • Next chapter: records and pattern matching — C# 9 records, which genuinely shipped before java2-7's own Java 16 version