SledgeHammer SledgeHammer - 2 months ago 19
C# Question

Why are web apps going crazy with await / async nowadays?

I come from a back end / thick client background, so maybe I'm missing something... but I recently looked at the source for an open source JWT token server and the authors went crazy with await / async. Like on every method and every line.

I get what the pattern is for... to run long running tasks in a separate thread. In my thick client days, I would use it if a method might take a few seconds, so as not to block the GUI thread... but definitely not on a method that takes a few ms.

Is this excessive use of await / async something you need for web dev or for something like Angular? This was in a JWT token server, so not even seeing what it has to do with any of those. It's just a REST end point.

How is making every single line async going to improve performace? To me, it'll kill performance from spinning up all those threads, no?

Answer

I get what the pattern is for... to run long running tasks in a separate thread.

This is absolutely not what this pattern is for.

Await does not put the operation on a new thread. Make sure that is very clear to you. Await schedules the remaining work as the continuation of the high latency operation.

Await does not make a synchronous operation into an asynchronous concurrent operation. Await enables programmers who are working with a model that is already asynchronous to write their logic to resemble synchronous workflows. Await neither creates nor destroys asynchrony; it manages existing asynchrony.

Spinning up a new thread is like hiring a worker. When you await a task, you are not hiring a worker to do that task. You are asking "is this task already done? If not, call me back when its done so I can keep doing work that depends on that task. In the meanwhile, I'm going to go work on this other thing over here..."

If you're doing your taxes and you find you need a number from your work, and the mail hasn't arrived yet, you don't hire a worker to wait by the mailbox. You make a note of where you were in your taxes, go get other stuff done, and when the mail comes, you pick up where you left off. That's await. It's asynchronously waiting for a result.

Is this excessive use of await / async something you need for web dev or for something like Angular?

It's to manage latency.

How is making every single line async going to improve performance?

In two ways. First, by ensuring that applications remain responsive in a world with high-latency operations. That kind of performance is important to users who don't want their apps to hang. Second, by providing developers with tools for expressing the data dependency relationships in asynchronous workflows. By not blocking on high-latency operations, system resources are freed up to work on unblocked operations.

To me, it'll kill performance from spinning up all those threads, no?

There are no threads. Concurrency is a mechanism for achieving asynchrony; it is not the only one.

Ok, so if I write code like: await someMethod1(); await someMethod2(); await someMethod3(); that is magically going to make the app more responsive?

More responsive compared to what? Compared to calling those methods without awaiting them? No, of course not. Compared to synchronously waiting for the tasks to complete? Absolutely, yes.

That's what I'm not getting I guess. If you awaited on all 3 at the end, then yeah, you're running the 3 methods in parallel.

No no no. Stop thinking about parallelism. There need not be any parallelism.

Think about it this way. You wish to make a fried egg sandwich. You have the following tasks:

  • Fry an egg
  • Toast some bread
  • Assemble a sandwich

Three tasks. The third task depends on the results of the first two, but the first two tasks do not depend on each other. So, here are some workflows:

  • Put an egg in the pan. While the egg is frying, stare at the egg.
  • Once the egg is done, put some toast in the toaster. Stare at the toaster.
  • Once the toast is done, put the egg on the toast.

The problem is that you could be putting the toast in the toaster while the egg is cooking. Alternative workflow:

  • Put an egg in the pan. Set an alarm that rings when the egg is done.
  • Put toast in the toaster. Set an alarm that rings when the toast is done.
  • Check your mail. Do your taxes. Polish the silverware. Whatever it is you need to do.
  • When both alarms have rung, grab the egg and the toast, put them together, and you have a sandwich.

Do you see why the asynchronous workflow is far more efficient? You get lots of stuff done while you're waiting for the high latency operation to complete. But you did not hire an egg chef and a toast chef. There are no new threads!

The workflow I proposed would be:

eggtask = FryEggAsync();
toasttask = MakeToastAsync();
egg = await eggtask;
toast = await toasttask;
return MakeSandwich(egg, toast);

Now, compare that to:

eggtask = FryEggAsync();
egg = await eggtask;
toasttask = MakeToastAsync();
toast = await toasttask;
return MakeSandwich(egg, toast);

Do you see how that workflow differs? This workflow is:

  • Put an egg in the pan and set an alarm.
  • Go do other work until the alarm goes off.
  • Get the egg out of the pan; put the bread in the toaster. Set an alarm...
  • Go do other work until the alarm goes off.
  • When the alarm goes off, assemble the sandwich.

This workflow is less efficient because we have failed to capture the fact that the toast and egg tasks are high latency and independent. But it is surely more efficient use of resources than doing nothing while you're waiting for the egg to cook.

The point of this whole thing is: threads are insanely expensive, so don't spin up new threads. Rather, make more efficient use of the thread you've got by putting it to work while you're doing high latency operations. Await is not about spinning up new threads; it is about getting more work done on one thread in a world with high latency computation.

Maybe that computation is being done on another thread, maybe it's blocked on disk, whatever. Doesn't matter. The point is, await is for managing that asynchrony, not creating it.

Comments