BuildQuill
C# learning path
23 of 25
Lesson 23 of 25
Lesson 23Beginner21 min

C# Async and Await

Keep a program responsive while waiting for I/O, understand Task as an eventual result, and avoid blocking, lost exceptions, and fake parallelism.

Before this lesson

  • Methods and return values
  • Exceptions
  • Files
  • Generics

Explain Task and await without a thread myth

Write an async method and propagate asynchrony to its caller

Recognize blocking and fire-and-forget hazards

The short answer

An async method can pause at await while an incomplete Task finishes, allowing its thread to do other work. It resumes later and produces a Task or Task<T>; async does not automatically create a new thread or make CPU work faster.

01

Waiting and computing are different work

Reading a remote response or file often spends most of its time waiting for another system. Blocking a server thread or UI thread during that wait wastes capacity or freezes interaction. Asynchronous APIs represent the unfinished operation so the caller can yield control and resume when completion arrives.

CPU-bound work actively uses the processor. Marking a heavy calculation async does not make it faster. Parallel execution and background scheduling are separate decisions with costs and thread-safety concerns.

WorkMain constraintTypical strategy
File/network/database I/OWaitingAsync APIs and await
Large calculationCPU timeOptimize; consider measured parallelism
Tiny quick operationNeitherKeep synchronous
02

Task represents eventual completion

Task means an operation will finish later without a returned value; Task<T> will eventually produce T. Calling an async method starts it and returns its task. await checks that task: if incomplete, the method returns control to its caller; after completion, execution continues after the await.

The starter uses GetAwaiter().GetResult only as an adapter for the course's older console compiler entry point. In a current .NET console project, prefer static async Task Main() and await PrepareReportAsync() so asynchrony flows naturally rather than blocking at the top.

C#
// Preferred in a current local .NET console project
static async Task Main()
{
  await PrepareReportAsync();
}
03

Async code should usually continue upward

When a method calls an asynchronous API, it should usually become async and return a Task to its caller. Blocking with .Result or .Wait can waste threads and can deadlock in environments with a synchronization context. ‘Async all the way’ keeps cancellation and failure visible.

Avoid async void except for event-handler signatures. A caller cannot await its completion or catch its later exceptions normally. Returning Task makes completion part of the method contract.

C#
static async Task<string> LoadMessageAsync(string path)
{
  string text = await File.ReadAllTextAsync(path);
  return text.Trim();
}

// Caller:
string message = await LoadMessageAsync("message.txt");
04

Concurrency is useful only when operations are independent

Starting two independent tasks before awaiting them can overlap their waiting time. Task.WhenAll waits for both. Do not parallelize operations that must occur in order or that mutate shared state without coordination.

Exceptions from awaited tasks are rethrown at the await and can be handled with normal try/catch. Cancellation is cooperative through CancellationToken: callers signal that results are no longer needed, and operations must observe the token. Timeouts, retries, and cancellation are related but distinct policies.

C#
Task first = Task.Delay(300);
Task second = Task.Delay(500);

await Task.WhenAll(first, second);
Console.WriteLine("Both completed");

Good habits

  • Name asynchronous methods with an Async suffix.
  • Return Task, not void, unless implementing an event handler.
  • Do not wrap naturally asynchronous I/O in Task.Run.
  • Pass cancellation through layers that can honor it.
  • Limit concurrency when processing an unbounded number of items.

Quick knowledge check

Answer before you reveal.

01Does async automatically run a method on another thread?

No. It enables non-blocking composition around Tasks; thread use depends on the operation and environment.

02Why is async void difficult outside event handlers?

Callers cannot await it, compose its completion, or observe exceptions through a returned Task.

Practice challenge

Now build it without copying.

Create two async methods that simulate loading profile and order data with different Task.Delay durations. Start both, await Task.WhenAll, and print a combined report only after both finish.

You are done when

  • Both operations are started before the combined await
  • Methods return Task<T> rather than async void
  • The final report uses both returned values

Stretch: Run the operations sequentially and concurrently, measure elapsed time with Stopwatch, and explain why the concurrent version approaches the longer delay rather than their sum.

Open challenge in playground

Lesson checkpoint

One small step locks it in

Mark this lesson complete, then keep the momentum going.

Complete and continue

Clear up the details

Frequently asked questions

What is the difference between concurrency and parallelism?

Concurrency means multiple operations are in progress with overlapping lifetimes. Parallelism means work executes at the same instant on multiple processing resources.

Should every method be async?

No. Use async when composing asynchronous operations. Pure calculations and quick synchronous work should remain synchronous.

When should I use ConfigureAwait(false)?

It is mainly a library-context concern. Application code should first follow its framework guidance; do not add it mechanically without understanding synchronization contexts.