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-...
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.
Additionally, many people have the misconception that async operations are somehow faster than their sync counterparts. In fact, they're generally slightly slower, but a fully asynchronous server will be able to maintain a much higher level of throughput, usually with similar latencies, than a fully synchronous server. In other words, the principal class of applications that benefit from async/await are high-concurrency server applications.
One more place I see these kinds of misconceptions is in naive benchmarks that purport to test async performance without using a concurrent workload. That tests only straight-line latency of individual requests and isn't the point of async. The benchmarks in the blog post above properly use a concurrent workload for testing.
An async method is much slower (40x) compared to a normal sync method. If you're writing a high performance library you need to be careful to not repeatedly call a method decorated with async in a tight loop. Of course 40x times slower of extremely fast is still really fast but I found the overhead can add up really quickly.
Good video on async/await and performance: https://channel9.msdn.com/Events/Build/BUILD2011/TOOL-829T
The reason for the performance hit is that a lot the time your async methods are actually completed synchronously, e.g. you read data from a FileStream with ReadDataAsync and instead of going to disk, the data was already cached in an in-memory buffer. When you hit that situation the overhead of the async - the state machine, not being able to inline the method, nesting everything in a try/catch, etc - makes it orders of magnitude slower than a normal method just fetching some data from a buffer.
If your method is always async and is already taking 2ms then it doesn't matter, but you want to avoid the async in methods called in tight loops in performance critical code. The overhead can be significant.
If the method is a small amount of "synchronous" code in a tight loop, then yeah, the overhead of the async call is going to outweigh any benefits (a few orders of magnitude slower).
However, if your method is doing something like reading in a 40MB file to do some processing and calls the ReadAsync methods then the difference will be negligible and you get the benefit of better parallel throughput.
A web server using few threads and async/await state machines can transfer those stack states out into managed heap objects too, and handle many times more concurrent requests without running out of RAM.
I think the Quasar API is not exactly what I described in my previous comment but the above building blocks are what is needed to achieve something close to it.
So I assume that means not using things like forkio/goroutines etc.. You still have to tell haskell "fork a green thread here" instead of what he wants, which is it to be completely transparent.
Additionally, I think one can make the case the haskell green threads aren't very different from async await, in which "awaiting" is just creating some data that a scheduler maps onto a threadpool (rather like how GHC maps it's 'green threads' onto it's 'capabilities' via a scheduler)
Furthermore, if you actually want to pass data round in Haskell rather than just fork threads, it leaves it completely up to the user to figure out how to do that.. MVar's are canonical but noisy syntatically. the 'async' package is very nice, but gives the gives the same syntax OP is complaining about!
So, for example, you could configure your web server to use use green threads to handle incoming http requests but configure other part of the system to run with real threads (for whatever reason).
In the end, internally, it will of course end up similar to async/await - you need to capture the execution state and resume it which is what async/await does. I just want to be able to do it without rewriting majority of my code.
I think the async/await model arrives as a reasonable compromise in terms of supporting an alternative to OS threads in that it allows you to get the benefits of green threads (i.e. that you can process a lot of different stack contexts simultaneously without needing to spin up a huge amount of OS threads) without getting into the messy situation of having a user-space scheduler pre-emptively switching green-thread stacks (which probably needs the scheduler itself to run on another OS thread and definitely needs to be done at a 'runtime' level) - instead, you just rely on co-operative scheduling, which can be done at a 'library' level.
this just seems like an odd feature imho, but sure i think i understand you better now.
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.
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
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...
You can achieve the same thing in a GUI app by simply off-loading the long-running operation to a separate thread. The same is _not_ true for a highly concurrent, I/O-heavy server workload. That's where async/await is necessary and not just a syntactic convenience.
A solution to this that I am particularly proud of can be found in the Nim programming language. It is a macro that transforms a single procedure into a synchronous and asynchronous procedure.
1 - http://nim-lang.org/news/e027_version_0_15_0.html#multisync-...
It's all about scale. If you're an Nginx server serving thousands of files concurrently, or a websocket server, you need async.
But if you can spare a thread for a connection to MySQL, it's the MySQL server that's going to be in trouble, because the server already uses a thread per connection.
C# async/await is like a DSL that generates the FSM while you write fluent sequential code, easy to write and easy to read.
Combined with this approach to debugging using `.load sos`:
The SOS debugging extension makes following JITted code much easier.
Of course if you're looking at async code, or the body of an iterator method, or a lambda body that's capturing variables, you need to understand those high-level transformations first.
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.
Writing your own synchronization context for these purposes is incredibly easy, as well:
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.
You should still be able to output an ID when using async/await. It just means that the calling thread can do something else while it waits for the DB to get back to it. Execution will resume in the same place.
As mentioned elsewhere in this thread, for a distributed system you can use GUIDs to remove a immediate dependency on the DB. You can adopt eventual consistency and merge later, but this adds lots of complexity. If you do go down this route, add the GUIDs as an additional key and stick with something sequential for PKs, to avoid clustering issues.
public Task<MyResult> ExecuteDatabaseQueryAsync()
MyResult result = await ExecuteDatabaseQueryAsync();
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.
It's not entirely clear what your problem is from that description. Why is the new ID not available when using async/await? If your "add a record" operation produces an ID in sync version, then the async version of it should not be considered logically completed until said ID is returned. Are you using some library that works differently?
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!
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 `<-`.
>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.
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.
But there are some optimizations and tricks. E.g. if the Task is already completed the async method will just continue to the next statement instead of attaching a continuation that will later be called.
And in case continuations are used async await will automatically capture the current SynchronizationContext and will assure that the continuation runs on the same.
I still wish they would've added a keyword for this. i.e. await-nocontext
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).