Skip to content
Lesson 4 of 9

Step 1 of 6 · Reading · ~3 min

Learn

Async and Concurrency

async/await Basics

A thread that calls a blocking API — file read, HTTP request, database query — sits idle until the response arrives. async/await lets that thread go do other work in the meantime, while your code still reads top-to-bottom like ordinary synchronous code.

using System.Threading.Tasks;

// db and api are stand-ins for real services
static async Task<int> FetchUserAge(int id) {
    var user = await db.GetUserAsync(id);        // I/O — method suspends here
    var profile = await api.GetProfileAsync(user);
    return profile.Age;
}

At each await, if the task isn't finished yet, the method SUSPENDS: control returns to the caller, and the rest of the method is scheduled to continue when the awaited task completes. No thread sits parked. Under the hood the compiler rewrites the method into a state machine — that's why the async keyword exists: it opts the method into that transformation.

The rules

  • An async method returns Task (no result), Task<T> (a result of type T), or — only for event handlers — void.
  • await someTask unwraps the task: awaiting a Task<int> yields an int. If the task failed, await re-throws the exception, so ordinary try/catch works.
  • await is only legal inside a method marked async.
  • return 42; inside an async Task<int> method — the compiler wraps the value into the task for you.

Trap 1: async void

An async void method can't be awaited, and its exceptions can't be caught by the caller — reserve it for event handlers, whose signature leaves no choice.

The entry point is its own story. Modern C# lets you write static async Task Main(), but that arrived in C# 7.1 and the Mono compiler behind this grader predates it: submitting one here fails with warning CS0028: 'Program.Main()' has the wrong signature to be an entry point followed by error CS5001. So the exercises in this course use a synchronous Main that blocks exactly once:

static void Main() {
    int total = SumAsync(new[] { 1, 2, 3 }).GetAwaiter().GetResult();
    Console.WriteLine(total);   // 6
}

Blocking at the very top of the program is the one place it is defensible: there is no caller left to starve and no captured context to deadlock against. Everywhere below it, await.

Trap 2: blocking on tasks

int age = FetchUserAge(42).Result;   // BAD — blocks, can deadlock
int age = await FetchUserAge(42);    // right

.Result and .Wait() block the calling thread until the task completes. In UI or classic ASP.NET contexts that's a classic deadlock: the task's continuation needs the very thread you just froze. Inside async code, always await. (The "Tasks in Parallel and Cancellation" lesson later in this course digs into the deadlock mechanics and ConfigureAwait.)

Sequential vs parallel awaiting

var w1 = await GetWeatherAsync("NYC");     // waits for NYC...
var w2 = await GetWeatherAsync("London");  // ...THEN starts London

var t1 = GetWeatherAsync("NYC");           // both in flight already
var t2 = GetWeatherAsync("London");
var both = await Task.WhenAll(t1, t2);     // total time = max, not sum

Awaiting one-by-one silently serializes independent work. Start the tasks first, then await them — the parallel-tasks lesson covers orchestration in depth.

Your exercise

Implement static async Task<int> SumAsync(int[] nums): first await Task.Delay(10); (a stand-in for real I/O), then return the sum — nums.Sum() works (System.Linq is already imported), or write a plain foreach loop. Main is already written: it reads space-separated integers, runs SumAsync to completion and prints the result.

For input 1 2 3 4 5 the grader expects exactly:

15

Bare number, nothing else. The grader-caught mistakes:

  1. Leaving the starter's placeholder return 0; in place — every test fails, printing 0 instead of 15, 60, 42.
  2. Rewriting Main as async Task Main or async void Main. The first does not compile on this grader (no async entry point in C# 7); the second lets the process exit during Task.Delay(10), printing nothing at all. Leave Main alone.
  3. Adding a label — sum: 15 fails. It's Console.WriteLine(total): the number alone on its line.
Up nextRecords and Pattern MatchingModern C#

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…