Is awaiting a Task instead of returning it directly in C# actually slower?
Vložit
- čas přidán 28. 03. 2021
- Become a Patreon and get source code access: / nickchapsas
Check out my courses: dometrain.com
Hello everybody I'm Nick and in this video I wanna talk about one of the things I see all the time regarding performance and awaiy async and that's whether you should await a task or when possible returning it directly. There are many opinions on the matter and here is mine.
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasGitHub
Follow me on Twitter: bit.ly/ChapsasTwitter
Connect on LinkedIn: bit.ly/ChapsasLinkedIn
Keep coding merch: keepcoding.shop
#dotnet #csharp #asyncawait
Another reason to use async await instead of simply returning is that you can get very sudden bugs. (with IDisposable for example)
The code below Will crash, because the DbContext will be disposed before the task actually returns.
Task GetUsers()
{
using var ctx = new DbContext();
return ctx.Users.ToArrayAsync();
}
This is actually a very good point! Thanks for pointing it out.
How will it get disposed earlier. Dont understand
@@EMWMIKE this might be only when function called without await. This is wrong example.
@@EMWMIKE when using the “using” keyword
in a code block , after that block the variable is garbage collected i think
@@nickchapsas at 8:31 you pointed to the post, where it was already written :) "consider adding a using, for example"
I only directly return a Task if the method contains only one or two lines (ie. is a relaying method).
As soon as the method has more things to do to do its job, I do use async await (and have my team do it as well).
This video does give an interesting understanding about how things work under the hood. As of now, i'll also consider whether or not i would want a method to be included in stack traces 🙂
So thanks a lot for this insightful video 👍🏻
I also prefer returning CompletedTask in overrides running completely sync instead of marking method async and behave like void. Rider doesn't like when you don't await anything in async method, and I don't like when Rider isn't happy).
Unfortunately, I can't fully agree with you on this one.
The async state machine that you talked about has two different modes. In Debug mode (i.e. code is not optimized), it will be effectively a reference type (mainly to provide better debugging support). In Release mode however, the state machine type will be a struct. It behaves like the following:
1) when the async method is called, the struct will get initialized and MoveNext will be called on it for the first time. This will usually result in an async operation being started.
2) when this async operation completes immediately, the state machine will return the result and set itself to state finished (I think -2 is the corresponding state indicator). The generated state machine itself will never be put on the managed heap in this scenario. This is what you actually measured in your benchmarks.
3) only if the async operation does not complete immediately (which is the expected default behavior of I/O operations), the state machine will be boxed and afterwards resides on the managed heap. Once the async operation signals that it completed, MoveNext will be called again on the state machine.
The problem is that the more async methods you have in the call chain, the more state machines will be placed on the managed heap. In usual scenarios, this might be negligible, but devs should keep this in mind.
Maybe you can do a memory benchmark on this in a future video? Where you actually determine the size of a state machine in managed heap with DotMemory?
This is 100% true but the video assumes that we are only talking about the Release version of the code, which is also what the benchmark is running on. All the timings and the memory collection and GC invocations are running under Release mode. So the observed memory and speed discrepancy is based on the optimized release version.
@@nickchapsas Yeah, but that's not what I'm complaining about. In your benchmark at the beginning, you return a completed task, or await it, respectively. The async state machine will never be boxed in this case. If you would use a task that is not completed, then you would see additional memory allocations in the method that is marked as async (and it will take longer to run, I suspect at least some microseconds). The basic statement "it only takes 20ns longer and allocates a few more bytes" is problematic.
@@IAmFeO2x Oh I see what you mean. Well observing that and showing it in a benchmark will be really hard to the point where you won't know if it's actually the state machine itself or a discrepancy within margin of error. Take the example at the end of this video for example. Even if the boxing makes it worse, it really is not observable (with an individual isolated use-case) and falls under my "Don't worry about it unless it becomes the next obvious thing to optimise".
@@nickchapsas yeah, I agree with you. Just wanted to point it out.
@@IAmFeO2x No no it's great that ou mention it because I actually have a video on boxing that I'm working on and I might actually include this point. Thanks!
Great video! I've been thoroughly enjoying all your videos, from the REST API tutorials to tips and tracks to these benchmarks. I think they all cover very useful topics in very useful ways. :)
Interesting results. Love these benchmarking videos.
There are stupidly edge cases where you actually need to worry about this, but must people won’t ever encounter then, I was once hired to improve some serializers performance so I did care about this, but even I have to admit this had the smallest impact of everything I did
true dude.
Amazing videos! I love learning these very specific topics!
Could you maybe cover DateTime vs DateTimeOffset?
Yet another great video! Thanks for that, it really helps me understand the matter more deeply and actually be able to talk to my seniors about those topics! 😆
Good video. I saw people in my team using try catch on a return task and not understanding why they dont hit the catch block
I was expecting a 2014 year video, amazing that only now we are getting good content on this stuff. Video very good, thanks.
Great video, thank you!
BTW, it will be interesting to have Fody weaver for it - in this case you can switch between return task or use await))
Nick, I love how you get under the hood of things-- checking the IL and so on. I've not only learned specific ideas from you, but improved my general ability to investigate C# code. But question-- in what universe will today's investigation matter in live code? Are you going to be awaiting 50,000 async tasks with the All modifier?
been wondering about this for a while, now I know for sure. great content man
Thank you!
Instead of arguing, why there is no meaning in the theoretical speed advantage of returning task (and sacrifice the Debugger) rather then await it -in the context of an app-, I'll just share your Video and take a Coffee.
Do you do mentorships? You are awesome!
Cool. Makes more sense to me now.
Especially after some typescript coding, where awaiting return is pointless.
Thank u, good video as always!
Seriously man...!!! i'm one of those guys who omits await whenever i can... I didnot realize how stupid this was until now..! This was super super helpful, thanks a lot...!
Nice video. I love this benchmarking videos.
Thanks for the video. Can you explain async/await state machine. It would be an awesome video.
Great video, thanks!
Interesting 🎉
Interesting outcome. still getting my head around async/await stuff. As for Benchmarkdotnet, I tried that at work but the work AV stopped it from working :(
Nice video, but I want to ask something else: forward methods? Is this some concept close to delegation/indirection and the like? Some wrapper object offloading the work to some other? Or is it something more?
Recent scenario I had with downloading image from an outside url rececieved in api order payload. My api response was up to 5+ seconds when using await to confirm image save was successful in my api response. Other option is to use a callback to check if image was ok or not while removing the await. This allows for an instant response from order api, but then i cant immediately confirm back if image was good. My callback flags order afterwards if image was bad. Customer is later notified instead of immediately. In this case, it all comes down to how soon you need to know and-or notify end user. Regardless, I almost always use await as it makes life much easier, and has much less lag when not working with images.
Γεια σου ρε Νίκο. Φοβερή δουλειά συνέχισε δυνατά.
Thank you!
Nice 😊...love it
Thanks, It will definitely help me for complex Method!
One Q. You know about NopCommerce ?
Content factory.☺️
Better than best.☺️
Man, I remember losing hours during a diagnose because the stack trace wasn't showing the correct call chain. Only reason I wouldn't use async\await is if there's a strong argument for a specific scenario.
Great video! Is there any concerns why this approach shouldn't be used in private methods?
I was concerned that you'd profiled this with a completed task, since there's short-circuits in the state machine for a completed task, but that last example was pretty compelling.
Interesting results, thanks.
Yeah so the original example uses a pre-computed task because it demonstrates raw state machine-based performance degradation, which is the point that people who say "don't use async await" make. Last example demonstrates a realistic scenario while still keeping all the calculation in memory, meaning that if it was an actual IO call, it would be even less visible.
I've never actually heard advice either way on this before but I followed the same reasoning myself that omitting the async await if possible should be quite a bit faster. Good to see that I was technically right but that the difference is so marginal that it doesn't matter for all but the hottest of hot paths.
Thanks man
you make such good content
I figure BenchmarkDotNet might not be the tool for returning results on this, but I would also expect that not async awaiting the task could also lock up the thread, and if so that'd definitely add time on processing when you're starting to look at network latency effects (which are almost certainly going to be in the order of at least milliseconds). I might be wrong, but I've always understood that being one of the reasons for calling it in that way.
This isn’t the case because the state machine isn’t actually needed there. The method will be awaited and the actual state machine that does the work (the one in the httpclient method) will be honoured. This does not affect behaviour in any and is purely performance and call stack.
@@nickchapsas Thank you for clarifying.
Cool video!
i would've loved if the video also showed a usecase where you wouldnt use async to make the nuance more clear vs the only text.
edit: indo get the difference remarkn s for a general video improvement
It is so good hat somebody does benchmarks you always wanted to make, but too lazy to really do it). What you think about ValueTasks do you find them useful? I think that things that really need some heavy parallel processing should go on lower level working with threads and synchronization primitives, and business logic should go with Tasks focusing on logic and never even thinking about should I await this Task or return as is, or should I use ValueTask here. Your research has broken my last doubts about stoping wasting my time paying attention to such microoptimizations.
There is a ValueTask video scheduled for next Monday, in which I explain how you can use ValueTask to get some nice memory wins.
Hey Nick thanks for the video.
David's list of guidelines also have "Avoid using Task.Run for long running work that blocks the thread" can you talk about this in a future video? He is using the Thread class in his example but to quote Stephen Cleary's book "As soon as you type new Thread() , it's over; your project already has legacy code"
What are your thoughts on this? Also in David's example of 'good code' what would be the right course if ProcessItem() method is async. Follow the same guideline?
I think I actually address this in a video I made a while ago about David's guidelines: czcams.com/video/lQu-eBIIh-w/video.html
@@nickchapsas Thanks nick. I don't think this particular guideline is mentioned in your video, but I'll give it another watch anyway 😊
@@IvarDaigon I still don't think you understand what this is about. There is nothing about Task.Run here. The Tasks in both cases are properly awaited. The difference is tha in one of the cases it's more efficient because the state machine isn't generated for the forward methods, (because it isn't needed). The code will work on a functional level exactly the same. I highy recommend you recreate the code locally and take a look.
Can you do a video on spin lock vs spin wait vs lock
Nothing new but very nice to see benchmarks to prove the point. Thanks
I got something new here. Thanks
So, there is like 1 more difference not mentioned in this video. It's when you introduce a bug into the code because the place where you remove await is using an IDisposable
like:
public -async- Task Example()
{
using(var disposable = GetDisposable())
{
return -await- disposable.SomethingAsync();
}
}
When you omit the await, the disposable will be disposed when you await it in a calling function.
You are right I wanted to add this in the video but forgot!
@Nick Chapsas In my experience, a Program that returns void does not wait for an await method to complete. The void Program is calling the await method and just returning immediately, so I think that last benchmark is flawed. You would need to compare two different programs. One Task Program calling an async Task method, and a void Program calling a Task method.
BenchmarkDotnet has built in behaviour to turn asynchronous when it detects an awaitable task as a benchmark. It’s not an async void and it won’t be treated like an event.
9:29 There is an error in IF condition/statements.
So, technically, SerializeAsyncNoAwait should be still faster =)
This stuff matters with async sequences or fast async while loops. There are many other differences, and we haven’t yet started on ValueTask…
Besides the godlike content you provide. I also enjoy your thumbnails. You look like you have a lot of fun doing them. Recommended you to my entire c# colleagues.
Which theme and/or color scheme are you using in Rider? tks
I actually have no idea. I think I might have made this one years ago after the VS dark theme
I like the debugging vs performance argument. But I am wondering what the impact of building and disposing of a Task is. Probably also not a lot. But still nice to know.
And: If you're going to use it synchronously, why use a Task?
A Task without they async keyword is still asyncrhonous as long as it is awaited at some point during the pipeline.
The reason I asked is that I have seen it go wrong. And I mean very wrong. A colleague got hung up on the importance of task and async without knowing what multithreading is about. So he ended up writing everything as tasks and awaiting all of it....to build an application that was entirely synchronized. He didn't even bother writing sync versions of anything, so in that project I got stuck using the same style of programming, that I (as a 25 year veteran who had written multithreaded programs the hard way) consider crackpot. By the time I joined the project he had made so much, it was not feasable to refactor...
And since then I have seen many examples just like that.
This was around the time that async and await were introduced. So there were many people saying how good and important it was. And it IS important, IF you know about multithreading. But if you don't, there's no telling how important because you don't know the why.
And of course, many of the examples found around the web didn't help either. Because they showed how to use the things. And what they did. But mostly in demonstration scenarios that don't show the why.
So, why use task and async when all of it is immediately synchronized again?
@@frankroos1167 I mean Task and Threading are different topics. They are kinda linked but asynchronous and parallelism are completely different discussions. It sounds like there is a lack of understanding to differentiate the two there.
@@nickchapsas I see your point. I'll wait for a video on it. Because it is still an interesting topic.
I would be wary if you had a synchronization context that would be need to use `.ConfigureAwait(false)`
I've been searching for this like everywhere. This is so confusing!
Interesting topic, but you forgot one more thing ;) In .Net core WebApi count of threads available from pool is limited, and with await they will be available for re-use earlier.
There isn't any threading involved here. The behavior in code is exactly the same as if async was there. The Task will be delegated and it will be awaited downstream without allocating more threads.
@@nickchapsasNope, the thread will be released later in one case. It will be released anyway, yes, but later. So in high load app it will lead to delayed processing of input request
I agree 100%. I literally had to refactor async into .net core api to increase load testing perfomance, the dev who wrote that was under the impression using async makes no difference
Nick is correct here, there is no practical behavior difference as there is no code following the final await, so there's no continuation to schedule
Well now i have to ask, what is a forward method?
A forward method is a method that all it really does is expose another method from something that the class has access to
Estaria padre un canal en español
Anyone could get me the David's article he mentions?
github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md
@@nickchapsas Thank you
I think is better to use Task.Delay in benchmark
It is actually note because Task.Delay isn’t guaranteed to be precise
Before watching comment: my understanding is that the runtime/compiler kinda handles any redundant awaits so both styles have the same behavior
This comment is awesome because it shows that there was a need for this video
I've had this argument too many times at work.
Stop trying to save nanoseconds.
It's just like those car improvement nutcases that remove bits of metal from the car instead of just having a diet.
So, basically, wrapping the call inside a try/catch would have achieved the same… without all the state machinery.
You only use async for stuff that is I/O bound, your http request took at least 50 ms and allocated A LOT more than 70 bytes. The async await overhead thus disappears into nothingness anyway.
My database requests in production take less than 1 ms
Also: If 20 nanoseconds are really a problem for you, then C# is probably not the right language for the job. C or Assembly would probably be a better pick haha
async Task Video()
{
await VideoTalksStream();
System.Console.WriteLine("See you in the next video");
await Task.Delay(1000);
System.Console.WriteLine("Keep Coding");
}
You need a return Task.CompletedTask;
@@kano636 why?
Good practices... I tend to use it always.
@@kano636 what the difference in functionality between return completed task and not return?
@@semen083 if you remove the async await, the return is mandatory, and you have it there. And it looks better if it has to return something, return this
When using tasks our rule is to always use async Task and await, that prevents any issue of non-awaited calls
Great video but please do not fall for the "Weird face thumbnails are funky" trend. Please?
Seems to be working out great. Need to catch your attention. My metrics say that just text on the thumbnail doesn't cut it :D
@@nickchapsas Yeah you're right. I tried to stop the progress, bu I'll admit it is futile. What is a bug against a giant wave? Food for thought.
Gotta be very, very careful with async/await and Tasks. If you sway from the recommended approaches even a bit you will get unexplained crashes and issues that are impossible to debug. Rule of thumb: always use async/await with Tasks.
Also people need to know why they are using async/await and why it exists. What benefits it brings. Why did Microsoft take their time to develop this crazy and complicated technology? Most people use it as if their program were completely synchronous and thus don't see too much direct benefit. They don't think about batching lengthy unrelated processes or other cool tricks you can do that would otherwise require spinning up a new thread. Or what benefits it brings to the web server.
Your examples are overly simplified. In the real world, the await is not slow because of state machine but because of context switching. We run online services which have many simultaneous clients sending short requests and whenever the code executes await, it risks losing its thread, because system decides to use it for some other task. Then the execution without redundant await commands is faster. And especially in .NET Framework in case you need to return to your original thread.
CZcams is a developer's nightmare - everyone wants to do videos instead of good ol articles which you can skim in 1 minute :)
Of course it's slower, lol, but that's not the point.
Wish there would be a benchmark comparison with ValueTask’s hot path.
8th of April is the scheduled release date for the ValueTask video