Why use `await AbcAsync()` instead of `Abc()`
I'm going through the tutorial in this page: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/controller-methods-views?view=aspnetcore-9.0
I don't understand this bit of generated code inside an async controller method:
why call
_context.SaveChangesAsync()
and then immediately await it, instead of the synchronous version _context.SaveChanges()
(which does exist)?
I assume that this is because the async method will only temporarily free the thread to do other work once it hits an await
, but that doesn't make sense, because that's the whole point of an async method (whether it uses await
inside its body or not).Part 6, controller methods and views in ASP.NET Core
Part 6, add a model to an ASP.NET Core MVC app
26 Replies
yes, using the async method allows the thread to do other work while waiting for something to happen outside of the CPU
in this case, network I/O to your database
but the controller method wrapping that piece of code is already async - wouldn't that be enough?
no, an async method that doesn't await anything is not actually async
oh huh
ultimately what makes it async is some code deep down in the call stack that starts a non-CPU-bound operation, which you await all the way back up the call stack
so unlike in javascript, where if you pass an async callback to something that expects a synchronous one, and where the default behavior is to not "wait" for the async function (so it will run asynchronously), c# "awaits" by default?
not really by default, you can start an async operation and store the returned
Task
in a variable to await
the result of laterso if the call on an async method doesn't block and returns an unfinished task, doesn't that mean it's running asynchronously?
The only thing the async keyword does is enable the use of await in the method body.
async methods run synchronously until the first
await
(Well, and it implicitly wraps the return value in a task)
maybe i should make it a point that this isn't about multithreading
this is about throughput
async/await allows you to avoid blocking threads when you don't actually have any work for them to do, (overwhelmingly, when doing IO)
so there's no concurrency going on, or at least there is but it's predictable and not done at a CPU level?
$nothread
There Is No Thread
This is an essential truth of async in its purest form: There is no thread.
it's useful in situations like web servers, where instead of taking a thread to handle a full request from start to end the actual sending/receiving of data can be done asynchronously and the same thread can juggle multiple requests when there is actually CPU work for the thread to do for them
which means your threads are doing a lot less waiting around for IO and your web server can handle more requests at once
also, notably in this case you cannot perform concurrent database operations with the same DbContext instance
well the continuations can run on whatever thread they want
uh what, bot seems messed up
Petris
REPL Result: Success
Console Output
Compile: 317.869ms | Execution: 98.090ms | React with ❌ to remove this embed.
well not in this case apparently
jcotton42
REPL Result: Success
Compile: 212.658ms | Execution: 16.611ms | React with ❌ to remove this embed.
I'm still on this, just wanted to add some more stuff that might help me later
it turns out that await doesn't necessarily yield to the calling function, but rather to the called function, until it hits non cpu-bound work.
in contrast:
I guess what really sticks out to me is
1. actually concurrent work depends completely on the called function. so I guess if you somehow "messed up" by not buying into async/await, your chain of callers will just be waiting for you synchronously
1. the whole point of async is efficiently doing I/O bound work, rather than "doing concurrent work" in general, and because of point 1, you have to hope that your call stack will hit an io bound function at some point, and an example of badly done async would be:
now I'm just wondering if, suppose
Main
is busy but C
returns, then B
will continue running until completion (or until it hits another await
), THEN back to Main
(the answer is: this is when concurrency kicks in and they run effectively at the same time)
thanks for linking this article!
would this be a good basic model for a web server?
not quite, if you
await
it in the loop your server will only process one connection at a time
this is actually a time where you wouldn't want to await it and just let it float away on the thread pool
(and add some mechanism to observe any exceptions that were thrown)actually I realized I just forgot to include yield in my mental model lol, like will it be doing things concurrently now, since there's nothing to wait for (otherwise handleConnection wouldn't know when to continue)?
if
HandleConnection
is async and immediately awaits
something, if you don't await
HandleConnection
itself then your loop and HandleConnection
will effectively run at the same timeoh right I forgot you have to
await Task.Yield()
, I thought Yield was magically doing that on its ownsince the loop isn't waiting for the result of the method it will just go straight back to listening, and when the method has work to do some thread on the thread pool will pick it up
note that if you never observe the
Task
representing the async operation (by awaiting, etc.) then you will never see if an exception is thrown by the method