
What lies beneath async/await in C#? - goorion
http://foreverframe.pl/what-lies-beneath-asyncawait-in-c/
======
caleblloyd
Async/Await is especially important to use in .NET and .NET Core web servers
if high concurrency is desired. .NET uses a managed thread pool that will only
spawn up to something like 50 threads quickly by default. If it sees that more
threads are needed, it will slowly add them.

This means that a Synchronous web endpoint (think API call that does not use
"Async/Await") will hog an entire thread for the life of the request. I
graphed the difference between Synchronous and Asynchronous API endpoints in a
blog post last year, and Synchronous endpoints were far less efficient:
[https://caleblloyd.com/software/net-core-mvc-thread-pool-
vs-...](https://caleblloyd.com/software/net-core-mvc-thread-pool-vs-async/)

Note that just because endpoints are declared as "async" does not
automatically make them efficient - Asynchronous I/O must be used all the way
to the lowest level. This can be an issue when using library code that you
don't control. For example, Oracle's official MySQL package provides "async"
methods, but it uses Synchronous I/O in these methods. Therefore, "async"
calls into their library will really cause synchronous performance.

Async/Await is a really cool feature of C#, but I wish that the language had
more protection that forced users to do things "the right way", like
disallowing Synchronous I/O in async methods. In order to write libraries that
do I/O properly that include both Synchronous and Asynchronous methods,
maintainers have to write two separate implementations of the same logic,
which is a bit of a pain.

~~~
jsingleton
I would argue that async/await is far more important for native applications.
I've seen plenty of apps doing significant work on the UI thread and hanging.
Making it easy for developers to run things in the background is great for
performance. Web browsers automatically add a level of asynchronous
interaction.

It's also important on a web server but it won't improve performance for a
single user in isolation. As you say, it allows more requests to be serviced
concurrently. This won't speed up an individual request unless the server is
already heavily loaded.

Edit: You don't need to build two library APIs. You can just build an Async
method as the default implementation then have a blocking call to that as the
synchronous method. I believe that all new MS APIs are now Async by default.

~~~
caleblloyd
> You don't need to build two library APIs. You can just build an Async method
> as the default implementation then have a blocking call to that as the
> synchronous method

This can lead to a nasty problem: Thread Pool Starvation. I contribute to
MySqlConnector, an MIT Licensed MySql driver for .NET that implements truly
Asynchronous I/O. We did this at first, but had to do a large refactor because
we hit Thread Pool Starvation in our high concurrency performance tests.
Here's the original issue: [https://github.com/mysql-
net/MySqlConnector/issues/62](https://github.com/mysql-
net/MySqlConnector/issues/62)

Stephen Toub has a pretty good article explaining why you shouldn't do "Sync
over Async":
[https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-i...](https://blogs.msdn.microsoft.com/pfxteam/2012/04/13/should-
i-expose-synchronous-wrappers-for-asynchronous-methods/)

------
rusanu
Writing high performance applications on Windows has been an 'open secret' for
long time. Good free presentation I know of are Rick Vicik ones:

    
    
      - https://blogs.technet.microsoft.com/winserverperformance/2008/04/25/designing-applications-for-high-performance-part-1/  
    
      - https://blogs.technet.microsoft.com/winserverperformance/2008/05/20/designing-applications-for-high-performance-part-ii/  
    
      - https://blogs.technet.microsoft.com/winserverperformance/2008/06/25/designing-applications-for-high-performance-part-iii-2/
    

A simplified explanation is that for good throughput one creates a completion
port and binds a thread pool to it with as many threads as CPUs. Then _all_ IO
is done via the completion port. If the system is partitioned (NUMA) then one
should create a completion port per node and partition the thread pool. I skip
the details, read the links for a better explanation. The gist of it that the
code written this way has no procedural flow when you read it. Instead of a
sequence of function calls (read socket -> parse -> read file -> write
socket), it must be a state machine which always reacts to some IO completion,
modifies the state, posts some more IO and then returns the thread back to the
pool (post socket read, socket read callback -> parse -> post file read, file
read callback -> post socket write, write socket callback). Writing it as FSM
makes it lot easier to understand.

C# async/await is like a DSL that generates the FSM while you write fluent
sequential code, easy to write and easy to read.

~~~
platz
so, what I'm seeing here is, use continuations.. i.e. async/await

------
Patient0
Also worth reading to understand how Async/Await are implemented:
[https://blogs.msdn.microsoft.com/ericlippert/2010/10/21/cont...](https://blogs.msdn.microsoft.com/ericlippert/2010/10/21/continuation-
passing-style-revisited-part-one/)

~~~
taspeotis
And [https://codeblog.jonskeet.uk/2011/05/08/eduasync-
part-1-intr...](https://codeblog.jonskeet.uk/2011/05/08/eduasync-
part-1-introduction/) is a useful reference, although I believe the
implementation details have changed slightly since those posts were written.

------
13of40
I'm a heavy C# user, but I come from a classical background and I have yet to
start using async/await stuff. My reason (and I know this is terrible and I
need to get over it) is I can't make heads or tails of it when I'm looking at
the jitted code in NTSD.

~~~
maxxxxx
I was in the same position but I would recommend you start using it. Make sure
you start with a small project and really step through each line so you can
predict what will run in which thread. I notice that some people use
async/await but don't really understand it which can cause problems in some
situations.

It definitely makes life much easier. Can't wait to use it in JavaScript.

~~~
jermaustin1
This, so much. My first deep dive into it I ended up in async hell.

Definitely start one call at a time in a small corner of a small application.
Once you understand async Task Method() vs async Task<Result> Method(), then
how the async methods all chain back together, you can expand it.

~~~
maxxxxx
The funny thing is you can use async successfully without having a clue. But
one day it will bite you hard.

~~~
naasking
This is really a problem for .NET itself. The pervasive availability of
thread-static state is the only way async/await could really go wrong.

~~~
maxxxxx
There is more that can go wrong. For example in a desktop app a button click
finishes before the async code has been executed. The code looks like it's
blocking but it isn't. I have seen that a few times now.

------
fekberg
I did a talk a while back on async/await in C# for anyone interested:
[https://vimeo.com/191077931](https://vimeo.com/191077931)

~~~
alashley
Thanks for posting! I believe you also have a Pluralsight course on the
subject, which would you recommend for someone who wants to dive deeper with
async/await?

~~~
fekberg
I'd certainly recommend the Pluralsight course, it goes into greater detail
and shows a lot more examples. Thanks for mentioning it!

------
kevingadd
One thing I really like about async/await is that it hooks into existing .NET
infrastructure for issuing calls on other threads. As a result, you can
implement your own synchronization context and control how the async callbacks
run and what thread they run on - if you have your own job scheduler, you can
run them inside that. Technically this means you can use async/await for
single-threaded workloads, by running all the callbacks yourself on the main
thread.

Writing your own synchronization context for these purposes is incredibly
easy, as well:
[https://github.com/sq/Fracture/blob/ed408e0a84afb0c68d7e81be...](https://github.com/sq/Fracture/blob/ed408e0a84afb0c68d7e81bec5f2dde0b869b413/Squared/TaskLib/TaskScheduler.cs#L153)

------
louprado
Any advice when saving new DB records ? We usually return the new ID in our
return result, but it is not available when using async/await. Is it possible
to send a second result with the ID and how does one create a listener for the
second result in Javascript ?

Edit: Because we use async HTTP requests, we aren't blocking the UI. On
further thought, the problem I described is not a good use case for
async/await.

~~~
tucaz
In addition to what nathanaldensr suggested I would add that you should try to
architect your application in a way that you don't have to wait for the DB to
give your ID back.

Using GUID as ID is an option, but there are plenty of other methodologies to
generate the ID in the client and not be held hostage by the DB to get your
IDs.

~~~
nathanaldensr
I was thinking of mentioning this, but I wanted to avoid given design advice
for systems I haven't seen. In virtually all of my own projects, I use
business-logic-generated GUIDs for IDs. There are a whole bunch of easily-
Googleable benefits for GUIDs over database-generated IDs.

------
voidlogic
Doing this with Go using goroutines and channels pretty simple:
[https://play.golang.org/p/fattakS6aj](https://play.golang.org/p/fattakS6aj)

But something struck me, the low level execution flow was much much more
intuitive. I think this might be an example how simple syntactic sugar can
make code much harder to reason about intuitively. I do write a lot of Go so
maybe its just that... you tell me!

~~~
dgfgfdagasdfgfa
> But something struck me, the low level execution flow was much much more
> intuitive.

A), I don't view this as lower level, just more verbose and explicit.

B), the logic is the same, but it's harder to read the operations—not knowing
go, `await` makes a lot more sense than `<-`.

~~~
voidlogic
Good feeedback, further thoughts:

>I don't view this as lower level, just more verbose and explicit.

Well in this case, aren't these the same? The Go flow shows the explicit
spawning of a (go/co)routine and the reading and wring to a thread safe queue.
The C# abstracts this so the Go is closer to what is actually happening (ie
low level). I should point out, you say more verbose, but there isn't much, if
any, of a line count hit here for Go compared to C#.

>but it's harder to read the operations—not knowing go, `await` makes a lot
more sense than `<-`.

Fair point, but if you swap <\- for await I'd think the Go is more clear for
people equally unfamiliar with Go and if you are learning Go <\- is a core
operator you will learn early.

------
chrisper
Is using the await keyword basically like a shortcut instead of having to add
a callback method to an asyncworker and then get the result manually?

~~~
j_s
I am always curious which enhancements to C# are just syntactic sugar vs.
requiring IL and/or CLR enhancements.

Microsoft released the CLR code required for async/await separately, enabling
back-compat for .NETv4.0. The F# language has supported it back to .NETv2.0.

[https://blogs.msdn.microsoft.com/bclteam/2012/10/22/using-
as...](https://blogs.msdn.microsoft.com/bclteam/2012/10/22/using-asyncawait-
without-net-framework-4-5/)

~~~
dgudkov
Async in F# works quite differently from C#. This article highlights some key
differences [1].

[1] [http://tomasp.net/blog/csharp-async-
gotchas.aspx/](http://tomasp.net/blog/csharp-async-gotchas.aspx/)

------
hyperc
Is it just me or someone else sticks the infamous .ConfigureAwait(false) on
every instance just to be safe? ;)

~~~
softwarefounder
This. And I hate doing this. I've talked myself out of the performance gains,
because it just makes everything feel verbose and hard-to-read.

I still wish they would've added a keyword for this. i.e. await-nocontext

~~~
orf
I think that would be a big mistake to add a keyword. It apparently doesn't
give much of a performance gain:

[https://stackoverflow.com/questions/13489065/best-
practice-t...](https://stackoverflow.com/questions/13489065/best-practice-to-
call-configureawait-for-all-server-side-code)

~~~
int_19h
It's not just about performance gain. The real problem is that if you don't
ConfigureAwait(false), you _don 't know_ where your continuation is going to
be executed, and how long it'll take to get there.

Worse yet, posting to the original sync context increases potential for
deadlocks. The UI thread often has to do something synchronously (e.g. invoke
an API that doesn't have an async version), thereby blocking the event loop.
If anywhere down the line it happens to wait on a task, and that task gets
posted to the UI synchronization context, you have a deadlock.

Note that the answer you link to is specifically for ASP.NET, where there's no
UI thread. But for libraries, you don't know if they're going to be used in a
web app or a GUI app. So, the safest thing is to always ConfigureAwait(false).

------
divs1210
Here's a async library I'm working on written on top of clojure/core.async.
Currently ~ 100 LOC.

[https://github.com/divs1210/conc-prim](https://github.com/divs1210/conc-prim)

~~~
vesinisa
I think you are being downvoted because that looks more like a parallelisation
library rather than an async/await implementation?

~~~
divs1210
Possibly. I did not claim it to be an async/await implementation, though.

