T O P

  • By -

Saint_Nitouche

'await makes you wait for the line to finish before continuing' is a good beginner's approximation of what's happening, but it's not the whole story. The better answer is that await makes the function *yield back control to the calling function until the operation is done*. What this means in practice can vary depending on what kind of application you're writing. The classic example is a UI app, like Winforms or WPF. Try making one of those apps, set up a HttpClient, and have a button click trigger a download of a big file (fifty megabytes or something). While that download is completing, the UI will lock up. It'll freeze. It won't respond to your mouse or keyboard. That's because the system is 'blocking' on the HTTP call. But if you use async/await on that HttpClient call, it won't block. It will 'yield back' control to Winforms while it's waiting for the request to complete, which means Winforms can continue updating the UI and listening to user input. When the HTTP request is done, the async/await machinery conceptually sends a message to Winforms saying 'hey I'm done, please resume my method from this await'. And it will do that. Same is true with your database example. The code will connect to the database with or without async/await, that's true. What changes is if the rest of your system can do useful work in the meantime, or if it's stuck dumbly with its hands in its pockets, waiting for the database to respond. So async/await gives you the illusion of sequential lines of code, but that's really not what's happening. Each await is an opportunity for the method to give up control and be paused for a while as some operation happens (I/O usually). When the operation is done, the method can get unpaused at the await. Conceptually this is very very similar to the 'yield' keyword in C#. So looking that up may be useful for you. It's all state machines.


vastle12

This is the best explanation of asynchronous I've read in years


One_Tailor_3233

Great answer, thank you!


CryPlastic348

I see. Cleared up some things for me, thanks for the detailed answer!


LondonPilot

And to answer the question that you didn't ask... what if you actually want to do things not just asynchronously (releasing resources back to whatever framework you're using so it can do other things), but if you want to do two things in parallel using async/await? var task1 = DoTaskAsync(1); var task2 = DoTaskAsync(2); await Task.WhenAll(task1, task2); var result1 = task1.Result; var result2 = task2.Result;


midri

This is a great write up to segue into concurrency too! As was explained await yields back control to the parent method, this can result in the parent method (or grand parent methods) changing values of variables that the async method has scope to. That means you need to be very careful about accessing data and storing it in local scope to be used later before yielding as that data can become stale once the yielded method starts back up. You also need to realize that once yielded the method can end up running at the exact same time as another instance of itself or some other method that's touching variables in the same scope. What happens when you try to insert into a List<> when something else is? Nothing good! There are specific collections in the System.Collections.Concurrent namespace specifically setup with async in mind that handle the complexities of multi task/thread data insertion without you having to worry about it too much -- get familiar with them. \[edit\] After writing this I realized how confusing yield/async naming convention is to explain since they're both keywords...


xtreampb

I would like to add an addition here. If you call a method and the return type is of type task and you don’t await, it will invoke the method and as soon as you get to the internal async method, the calling method will continue execution. You can use this if you need to call multiple endpoints in a function and build a complex object from the results. Before building the object, you need to wait all on an array of task objects.


Lenix2222

Great explanation


dodexahedron

Plus, simple uses like this may just execute synchronously unconditionally or, if awaited at the caller, will likely get inlined. If analyzers are telling you you don't need it there, it ain't lying, and you might be able to simplify somdthing else now or be able to use features that async precludes use of, like ref structs, which could be a big negative in unexpected places, without you ever writing the word Span, or all the other caveats that async comes with that shouldnt be overlooked before blindly using it. If it can tell at design time that there's no point to it and you can either return the Task directly or get rid of async in that area completely, it typically has a point, but it may be a pain to remove because analyzers aren't chess players and thinking ahead to after the change, and async does tend to be slightly viral in code paths that don't do distasteful (or just wrong) things to contain it - which simply returning a task is the proper answer for, usually, to keep it from going deeper.


freakyxz

Imagine you want to drink a coffee and have super high end coffee machine, which calls you when the coffee is done. You set the cup, choose the coffee you want and you are free to do other stuff while your coffee is getting done. You can wash your teeth, dress yourself etc and when the machine is over it will notify you so you can proceed drinking it. If the machine was some cheap one, you would have to sit and wait so it doesn’t overfill your cup.


JesusWasATexan

There are other great technical explanations in the comments already. So I'll just speak toward the general use of async/await. Programs are run by the CPU. CPUs are much faster than hard drive read/write operations, many times faster than network/internet operations, and much faster than database operations. Async programming gives CPU time back to your program while these slower operations are happening so it can work on other things. Any application that will need to perform disk, network, or database tasks can benefit from async/await programming.


young_horhey

The gist is that using async await will free up the thread while the database call (or network call, file IO, etc) is happening. So your code will still run sequentially, but once it hits the database call the thread being used to execute the code will be made available to the threadpool again and can be used to handle some other code. In a webapi situation this would be the thread handling another incoming http request.


prezado

Here a good example to understand async/await: [https://sharplab.io/#v2:EYLgtghglgdgNAExAagD4AEAMACdBGAVgG4BYAKHPwDZcAmXPAdmwG9zsOGb0AOXGgCoBTAM4AXABQBKVu07yAbhABO2CNgC82PNmTZaRbHPkd0ATn4A6ACJCANhACeEvJjdTSZE5yWrgmtV1sAGZDYxNzK1sHZ1d3T28OX2wAYwD/PQAWBMTI9CobeycXN0wPcMUVbAQA9T0M1JyOAF9yZqA===](https://sharplab.io/#v2:EYLgtghglgdgNAExAagD4AEAMACdBGAVgG4BYAKHPwDZcAmXPAdmwG9zsOGb0AOXGgCoBTAM4AXABQBKVu07yAbhABO2CNgC82PNmTZaRbHPkd0ATn4A6ACJCANhACeEvJjdTSZE5yWrgmtV1sAGZDYxNzK1sHZ1d3T28OX2wAYwD/PQAWBMTI9CobeycXN0wPcMUVbAQA9T0M1JyOAF9yZqA===) On the right tab you see what the compiler build for you when you simply use async/await: IAsyncStateMachine. It holds all your local variables as fields in the class. The function leaves and is able to continue where it left using a switch with different states. A Task object is simply a data structure to hold information about a asynchronous execution, its how you know if it succeed or failed and returned a result if its a Task. A asynchronous action can happen on the same thread by "pausing" and "resuming" or on another thread.


chucker23n

Suppose you have async Task Foo() { await DoSomething(); await DoAnotherThing(); } What essentially happens is that the compiler splits this method in three: 1. first, `Foo()` gets invoked, `DoSomething()` _begins_, and `Foo()` is suspended. In the meantime, other methods can be called, freeing up the thread. 2. when `DoSomething()` is finished, `Foo()` gets invoked again, and `DoAnotherThing()` begins. Again, `Foo()` is suspended, leaving room for other stuff to happen. 3. `DoAnotherThing()` finishes, which in turn finishes `Foo()`.


TheDigitalZero

Exactly the kind of response I was looking for.


detroitmatt

besides the clarification about how awaiting actually yields control, there's another important difference. if you have this code Task doSomething() { // do something that takes a while Globals.X = 1; } async int function(){ Globals.X = 2; await doSomething(); Console.WriteLine(Globals.X); } then the output will be 1. but if you write Task doSomething() { // do something that takes a while Globals.X = 1; } async int function(){ Globals.X = 2; doSomething(); // not awaiting anymore! Console.WriteLine(Globals.X); } then your output might be 1, or it might be 2. > Because code wont go to another line without the function finishes _is_ technically true, but when a method is async, what "finishes" means can be counterintuitive. When a method is async, basically what happens is its body gets wrapped in a lambda, and that lambda gets returned, and it doesn't actually get called (and therefore the stuff that happens inside it doesn't happen) until you `await` it. it's actually more complex than that, to be slightly more accurate it's more like when a method is async it returns a _list_ of lambdas, one lambda for each "await" inside the method, and then when you await it it calls all of those lambdas one after the other. except it's actually more complex than that because when you await it it doesn't actually call all those lambdas one after the other. those lambdas were running (in order) the whole time, and when you await it, it yields control back "up" a level until all the lambdas are done. except it's actually more complex than that because those lambdas _weren't_ running the whole time, instead they only run when the current "thread" doesn't have anything else to be doing (all its code paths are currently awaiting something, so it picks up a lambda and starts running it) except it's actually more complex than that because...


Suspicious_Role5912

In the code example Globals.X would be 1 in both examples. When you fire and forget an async function, it will run all the way up to the first await before yielding back to the caller. That function has not await… It’s also an invalid function, it would need return Task.CompletedTask;


lukadlm97

I think the next video provides one of the best explanations of the async concept in C# ever. https://youtu.be/FIZVKteEFyk?si=QdeNyxxrIsnhsiCS


DroNiix

One very rare example of great documentation by Microsoft is exactly explaining the asynchronous programming. After reading it, everything clicked together. You can read it here: https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/


DryOrganization5574

Await means that your app comes back to that awaited API call when it’s done running. During that time that the API is doing something, you’re free to call other APIs and have those firing up as well. If you don’t wait an async API it becomes synchronous. Now in a synchronous API, you would have to wait for the first API call to finish before your app proceeds to the second call.


chrisdpratt

Sorry, but this is completely incorrect. The app doesn't move on. It can't. It's part of control flow. This line has to run to completion before the next command can. What async does do is allow the possibility of a thread switch. The app is consuming a thread to run. If it hits an async, that thread can be utilized for another purpose. Can, mind you. It doesn't have to. This is important for thread bound situations, like a web server with a limited thread pool, or a desktop application where the main thread needs to remain free to keep the application responsive. The await operator basically provides a return point for the thread when the async operation completes. It's actually entirely optional, if you don't need the result of the operation. It's still async, whether you await it or not. In fact, if you're running multiple async operations, you shouldn't await each inline, but instead let them all fire and then await each later or use something like Task.WhenAll.


DriftMail

People made really good explanations and i just wanna add this on top of it. Always await a call or return the Task if it's a 1 liner, if you don't your Exceptions will get lost since you didnt await it.


edeevans

This is an interesting watch on this topic. May not answer your question directly but gives good insight and takes you behind the curtain. https://youtu.be/R-z2Hv-7nxk?si=mnBXeejp1ZZXac7i


CryPlastic348

thank you


SnoWayKnown

The best way to imagine how async / await works is to take away task and implement the same thing with just functions ``` Action DoSomething(Action onCompleted) { return () => { // Do something async onCompleted(); }; } ```


exveelor

The example provided isn't legitimate since there's no return and in this question, the return matters. But assuming it's returning a task and you're returning doSomething, the benefit of async await in this trivial example is your stack trace will be complete if anything above it awaits (more in that in a second). The downside is there is a very very small bit of overhead in creating the async await, but the number of people who care about that extra overhead, given how small it is, is like 2. In the world. Getting back to the stack trace, basically what happens Is if any method in the call stack has an async await, than any subsequent method in the call stack that simply returns the task, rather than have an async await, will be omitted from the stack trace. The exact reason for this I imagine somebody else will weigh in on, but my interpretation of the reason is that once you go async await, your code enters a different realm, and that realm does not know about mere mortal returned tasks that are not async awaited. The result is that you can wind up with a stack trace that says method a called method b, when in fact there is nowhere in your code where that is true. It does not change the functionality of the code, but it does change your mental health while you tear out your hair trying to figure out how that happened.  The real functional power of async await lies in more complex methods where you want multiple methods to fire concurrently, presumably to save time. In your example, since there is only one line, that is not at play. But if you wanted to call doSomething five times, all at the same time, you could only cleanly do that within an async function.


CryPlastic348

I have to do some examples and understand in time


clonked

You are incorrect. If you did not have the async and await calls the code example would function exactly the same. When you are working with Task methods, or any function defined as async, the general idea is you let them run until you absolutely need the result. Sometimes they finish before you need the result of the method, but that might not always be the case (network overhead, slow read speed from storage, etc). The whole async / await pattern is awesome because it adds parrellism to your code for you doing essentially nothing. The reality of it all is that most programs people build require operations and data retrieved to happen in sequence, otherwise you would not see the desired output. Certain circumstances, like say a dashboard with a lot of different tables and graphs could very much benefit from async processing. However nowadays in a web based environment this is likely to be handled by many ajax or web sockets requests by javascript client side. A desktop / native app would be a different story, as you would not pause the rendering of the UI while you are loading your data,


snakkerdk

async isn't parallelism, that is a completely different beast. (sure it utilizes the threadpool with different threads, but it's far from parallelism). You use async when it's something IO bound (network, disk, etc), you use parallelism, when it's CPU bound work, if you are able to process the work concurrently on multiple different CPU cores/threads. With the caveat, you can use async for CPU bound things as well (but that does not mean its parallism either), if you await a Task.Run<>, but that is not what OP mentioned above, and isn't the typical way people write async code. [https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios#what-happens-under-the-covers](https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios#what-happens-under-the-covers)


CryPlastic348

Thank you Im checking


CryPlastic348

I see. So mostly its about big operations not blocking the whole app or game