Why use async when I have to use await?

Does that not defeat the purpose of offloading a task to a thread without waiting for it to finish?

Yes, of course. But that’s not the purpose of await/async. The purpose is to allow you to write synchronous code that uses asynchronous operations without wasting threads, or more generally, to give the caller a measure of control over the more or less asynchronous operations.

The basic idea is that as long as you use await and async properly, the whole operation will appear to be synchronous. This is usually a good thing, because most of the things you do are synchronous – say, you don’t want to create a user before you request the user name. So you’d do something like this:

var name = await GetNameAsync();
var user = await RemoteService.CreateUserAsync(name);

The two operations are synchronous with respect to each other; the second doesn’t (and cannot!) happen before the first. But they aren’t (necessarily) synchronous with respect to their caller. A typical example is a Windows Forms application. Imagine you have a button, and the click handler contains the code above – all the code runs on the UI thread, but at the same time, while you’re awaiting, the UI thread is free to do other tasks (similar to using Application.DoEvents until the operation completes).

Synchronous code is easier to write and understand, so this allows you to get most of the benefits of asynchronous operations without making your code harder to understand. And you don’t lose the ability to do things asynchronously, since Task itself is just a promise, and you don’t always have to await it right away. Imagine that GetNameAsync takes a lot of time, but at the same time, you have some CPU work to do before it’s done:

var nameTask = GetNameAsync();

for (int i = 0; i < 100; i++) Thread.Sleep(100); // Important busy-work!

var name = await nameTask;
var user = await RemoteService.CreateUserAsync(name);

And now your code is still beautifuly synchronous – await is the synchronization point – while you can do other things in parallel with the asynchronous operations. Another typical example would be firing off multiple asynchronous requests in parallel but keeping the code synchronous with the completion of all of the requests:

var tasks = urls.Select(i => httpClient.GetAsync(i)).ToArray();

await Task.WhenAll(tasks);

The tasks are asynchronous in respect to each other, but not their caller, which is still beautifuly synchronous.

I’ve made a (incomplete) networking sample that uses await in just this way. The basic idea is that while most of the code is logically synchronous (there’s a protocol to be followed – ask for login->verify login->read loop…; you can even see the part where multiple tasks are awaited in parallel), you only use a thread when you actually have CPU work to do. Await makes this almost trivial – doing the same thing with continuations or the old Begin/End async model would be much more painful, especially with respect to error handling. Await makes it look very clean.

Leave a Comment