20

I've asserted my understanding of async/await on a couple of occasions, often with some debate as to whether or not I'm correct. I'd really appreciate it if anyone could either confirm or deny my understanding, and clear up any misconceptions so that I don't spread misinformation.

High-Level Understanding

async/await is a way of avoiding callback hell while writing asynchronous code. A thread that is executing an asynchronous method will return to the thread pool when it encounters an await, and will pick up execution once the awaited operation completes.

Low-Level Understanding

The JIT will split an asynchronous methods into discrete parts around await points, allowing re-entry into the method with method's state preserved. Under the covers this involves some sort of state machine.

Relation to Concurrency

async/await does not imply any sort of concurrency. An application written using async/await could be entirely single-threaded while still reaping all the benefits, much the way that node.js does albeit with callbacks. Unlike node.js, .NET is multi-threaded so by having async/await, you get the benefits of non-blocking IO without using callbacks while also having multiple threads of execution.

The Benefits

async/await frees up threads to do other things while waiting for IO to complete. It can also be used in conjunction with the TPL to do CPU bound work on multiple threads, or off the UI thread.

In order to benefit from non-blocking IO, the asynchronous methods need to be built on top of API's that actually take advantage of non-blocking IO which are ultimately provided by the OS.

Misuse

This is the biggest point of contention in my understanding. A lot of people believe that wrapping a blocking operation in a Task and using async/await will bring about a performance increase. By creating an additional thread to handle an operation, returning the original thread to the thread pool, and then resuming the original method after the task completes, all that's occurring are unnecessary context switches while not really freeing up threads to do other work. While this isn't as much a misuse of async/await as it is the TPL, this mindset seems to stem from a misunderstanding of async/await.

Community
  • 1
  • 1
w.brian
  • 16,296
  • 14
  • 69
  • 118
  • 1
    Your understanding is perfect, 100% correct. – Deepak Bhatia Aug 08 '15 at 23:15
  • 2
    It's the compiler, rather than the jitter, that creates the state machine; there's no such thing as `await` at the level below. You can examine the classes produced if you use ILSpy or similar. – Jon Hanna Aug 08 '15 at 23:35
  • About your misuse: if your thread has nothing else to do, then indeed you should let your thread to it. Buf if your thread has other meaningful things to do, for instance keep your UI responsive, then it is wise to do the heavy work in a separate task – Harald Coppoolse Aug 10 '15 at 10:24

1 Answers1

15

That's pretty much correct.

A few notes though:

  • The thread that starts executing an async method is the caller's thread which may, or may not, be a ThreadPool thread.
  • If an await is reached, but the awaitable (usually Task) is already completed the thread will continue executing the rest of the method synchronously.
  • The thread that resumes running the method is usually a ThreadPool thread, but that depends on the SyncrhonizationContext and TaskScheduler.
  • The JIT isn't involved here (not more than usual). The compiler is the one that turns an async method into the state machine. You can see that with this TryRoslyn example.
  • It's true that async-await doesn't necessarily imply concurrency as it could be single threaded. However, it can still be concurrent even with just a single thread by starting and awaiting multiple asynchronous operations at the same time.
  • async-await and the TPL are not completely separate parts. async-await is built on top of the TPL. That's why it's called the Task-based Asynchronous Pattern.
  • While most truly asynchronous operations are I/O, not all are. You also usually delay asynchronously with Task.Delay or use asynchronous synchronization constructs like SemaphoreSlim.WaitAsync.
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    Wow, great answer. Could you clarify one thing? "it can still be concurrent even with just a single thread by starting and awaiting multiple asynchronous operations at the same time." How is this possible? Are there threads being spawned outside of the .NET runtime? – w.brian Aug 09 '15 at 00:12
  • 1
    @w.brian "concurrent" here doesn't mean multi-threaded. It means multiple operations being done at the same time (i.e. concurrently). Now if your operations are asynchronous and so don't require a thread then you can have concurrent operations with just a single thread. http://stackoverflow.com/a/10495458/885318 – i3arnon Aug 09 '15 at 00:17
  • 2
    @w.brian the concurrency of a single thread is at the level of I/O hardware - a single thread can launch (and await) multiple I/O operations, while in synchronous mode it would have to wait (while blocking) for each one to end before it can launch the next one. – shay__ Aug 09 '15 at 05:58