Async and Await with Task
C#'s asynchronous programming model, and why it exists.
What you'll learn
- Explain why async/await exists: not blocking a thread while waiting on slow I/O
- Mark a method `async` and return `Task`/`Task<T>` instead of `void`/a plain type
- Use `await` to receive a Task's result once it completes
Explanation
Many real operations a C# program performs -- reading a file, calling a web API, querying a database -- take time and mostly involve waiting, not computing. Doing that waiting synchronously ties up an entire thread for the whole wait, which doesn't scale: a server handling many requests would need one thread sitting idle per in-flight request. C#'s async/await model lets a method start a slow operation, give its thread back for other work while waiting, and resume exactly where it left off once the result is ready -- without you manually managing callbacks or threads yourself.
A Task represents an operation that may not have finished yet -- roughly, a "future result." A method that returns a value asynchronously is declared async Task<T> instead of a plain T (or just async Task for an asynchronous method with no return value, instead of void). Inside an async method, the await keyword pauses that method (without blocking its thread) until the awaited Task completes, then resumes with its result: int result = await SomeAsyncMethod();.
Calling an async method from the top level of a modern C# program (top-level statements) can use await directly -- the compiler wraps the whole file's top-level code in an async entry point automatically when it detects await used there.
A method's asynchrony is genuinely part of its signature and tends to be contagious in a good way: once a method awaits something, it should itself be async, and its callers who need its result generally need to await it too, all the way up the call chain -- this is a real, deliberate design tradeoff, trading a bit of verbosity for a program that never silently blocks a thread it didn't mean to.
Guided lab
Guided edit: From a synchronous call to async/await
Follow each step to see how making this function asynchronous changes (or doesn't change) its output.
Step 1 of 2
Start with a plain synchronous function call -- no async yet.
using System;
int Square(int n)
{
return n * n;
}
int result = Square(6);
Console.WriteLine($"Result: {result}");Stuck? Get a hint.
Common mistakes
- Blocking on a Task synchronously (calling `.Result` or `.Wait()` instead of `await`) -- this defeats the purpose of async and can even deadlock certain application types.
- Marking a method `async` but forgetting it must return `Task`, `Task<T>`, or `void` (only for event handlers) -- an `async` method can't just return a plain type like `int` directly.
- Assuming `async`/`await` always creates a new OS thread -- it doesn't; it's fundamentally about not blocking the current thread while waiting, not about running work in parallel.
Knowledge check
Takeaway
Use `async`/`await` (with `Task`/`Task<T>` return types) to avoid blocking a thread during a slow operation -- and let asynchrony propagate up the call chain via `await` rather than blocking on `.Result` or `.Wait()`.
Summary
async/await lets a method give up its thread while waiting on a Task instead of blocking it; async methods return Task or Task<T>, and await resumes with the result once the awaited Task completes.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.