Is awaiting a Task instead of returning it directly in C# actually slower?

Sdílet
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

Komentáře • 123

  • @ElSnakeObwb
    @ElSnakeObwb Před 3 lety +53

    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();
    }

    • @nickchapsas
      @nickchapsas  Před 3 lety +11

      This is actually a very good point! Thanks for pointing it out.

    • @EMWMIKE
      @EMWMIKE Před 2 lety +1

      How will it get disposed earlier. Dont understand

    • @vifvrTtb0vmFtbyrM_Q
      @vifvrTtb0vmFtbyrM_Q Před 2 lety +2

      @@EMWMIKE this might be only when function called without await. This is wrong example.

    • @sc12sc
      @sc12sc Před 2 lety

      @@EMWMIKE when using the “using” keyword
      in a code block , after that block the variable is garbage collected i think

    • @saltukkos
      @saltukkos Před rokem

      @@nickchapsas at 8:31 you pointed to the post, where it was already written :) "consider adding a using, for example"

  • @Assgier
    @Assgier Před 3 lety +37

    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 👍🏻

    • @iGexogen
      @iGexogen Před 3 lety +3

      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).

  • @IAmFeO2x
    @IAmFeO2x Před 3 lety +30

    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?

    • @nickchapsas
      @nickchapsas  Před 3 lety +4

      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.

    • @IAmFeO2x
      @IAmFeO2x Před 3 lety +9

      @@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.

    • @nickchapsas
      @nickchapsas  Před 3 lety +5

      @@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".

    • @IAmFeO2x
      @IAmFeO2x Před 3 lety +4

      @@nickchapsas yeah, I agree with you. Just wanted to point it out.

    • @nickchapsas
      @nickchapsas  Před 3 lety +6

      @@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!

  • @stuzyx906
    @stuzyx906 Před 3 lety

    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. :)

  • @learncodingbycoding
    @learncodingbycoding Před 3 lety +23

    Interesting results. Love these benchmarking videos.

  • @willinton06
    @willinton06 Před 3 lety +19

    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

  • @alphaios7763
    @alphaios7763 Před 3 lety +5

    Amazing videos! I love learning these very specific topics!
    Could you maybe cover DateTime vs DateTimeOffset?

  • @chrisd961
    @chrisd961 Před 3 lety +3

    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! 😆

  • @Lior_Banai
    @Lior_Banai Před 3 lety +3

    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

  • @helicalius
    @helicalius Před 3 lety +5

    I was expecting a 2014 year video, amazing that only now we are getting good content on this stuff. Video very good, thanks.

  • @vasiliyfofanov2605
    @vasiliyfofanov2605 Před 3 lety

    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))

  • @benjamininkorea7016
    @benjamininkorea7016 Před rokem +1

    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?

  • @chriscardwell3020
    @chriscardwell3020 Před 3 lety

    been wondering about this for a while, now I know for sure. great content man

  • @TuxCommander
    @TuxCommander Před 3 lety

    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.

  • @josephizang6187
    @josephizang6187 Před 3 lety +3

    Do you do mentorships? You are awesome!

  • @ilyakurmaz
    @ilyakurmaz Před 3 lety

    Cool. Makes more sense to me now.
    Especially after some typescript coding, where awaiting return is pointless.
    Thank u, good video as always!

  • @aj.arunkumar
    @aj.arunkumar Před 2 lety

    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...!

  • @alexpablo90
    @alexpablo90 Před 3 lety

    Nice video. I love this benchmarking videos.

  • @ValueLevit
    @ValueLevit Před 3 lety

    Thanks for the video. Can you explain async/await state machine. It would be an awesome video.

  • @RoiTrigerman
    @RoiTrigerman Před 3 lety

    Great video, thanks!

  • @nklaveren
    @nklaveren Před 8 měsíci

    Interesting 🎉

  • @harag9
    @harag9 Před 2 lety

    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 :(

  • @kostasgkoutis8534
    @kostasgkoutis8534 Před 3 lety

    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?

  • @kenjacobi9154
    @kenjacobi9154 Před 3 lety

    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.

  • @evanboltsis
    @evanboltsis Před 3 lety

    Γεια σου ρε Νίκο. Φοβερή δουλειά συνέχισε δυνατά.

  • @ayudakov
    @ayudakov Před rokem

    Thank you!

  • @vivekgowda1576
    @vivekgowda1576 Před 3 lety

    Nice 😊...love it

  • @BK-19
    @BK-19 Před 3 lety

    Thanks, It will definitely help me for complex Method!
    One Q. You know about NopCommerce ?

  • @sohampatel1063
    @sohampatel1063 Před 3 lety +12

    Content factory.☺️
    Better than best.☺️

  • @efrenb5
    @efrenb5 Před rokem

    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.

  • @ogy23
    @ogy23 Před rokem

    Great video! Is there any concerns why this approach shouldn't be used in private methods?

  • @0shii
    @0shii Před 3 lety +4

    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.

    • @nickchapsas
      @nickchapsas  Před 3 lety +3

      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.

  • @lexer_
    @lexer_ Před 2 lety

    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.

  • @ARS-fi5dp
    @ARS-fi5dp Před rokem

    Thanks man

  • @navrim
    @navrim Před 2 lety

    you make such good content

  • @sasukesarutobi3862
    @sasukesarutobi3862 Před 3 lety

    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.

    • @nickchapsas
      @nickchapsas  Před 3 lety +1

      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.

    • @sasukesarutobi3862
      @sasukesarutobi3862 Před 3 lety

      @@nickchapsas Thank you for clarifying.

  • @antonmartyniuk
    @antonmartyniuk Před 3 lety

    Cool video!

  • @powerclan1910
    @powerclan1910 Před 2 lety

    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

  • @iGexogen
    @iGexogen Před 3 lety

    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.

    • @nickchapsas
      @nickchapsas  Před 3 lety +1

      There is a ValueTask video scheduled for next Monday, in which I explain how you can use ValueTask to get some nice memory wins.

  • @superpcstation
    @superpcstation Před 3 lety

    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?

    • @nickchapsas
      @nickchapsas  Před 3 lety

      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

    • @superpcstation
      @superpcstation Před 3 lety

      @@nickchapsas Thanks nick. I don't think this particular guideline is mentioned in your video, but I'll give it another watch anyway 😊

    • @nickchapsas
      @nickchapsas  Před 3 lety

      @@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.

  • @roflex2
    @roflex2 Před 3 lety +1

    Can you do a video on spin lock vs spin wait vs lock

  • @user-wy5uv6zx4t
    @user-wy5uv6zx4t Před 2 lety

    Nothing new but very nice to see benchmarks to prove the point. Thanks

  • @michaelameyaw1746
    @michaelameyaw1746 Před 3 lety

    I got something new here. Thanks

  • @Rolandtheking
    @Rolandtheking Před 3 lety

    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.

    • @nickchapsas
      @nickchapsas  Před 3 lety

      You are right I wanted to add this in the video but forgot!

  • @Bundynational
    @Bundynational Před 3 lety

    @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.

    • @nickchapsas
      @nickchapsas  Před 3 lety +3

      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.

  • @wilbit
    @wilbit Před rokem

    9:29 There is an error in IF condition/statements.
    So, technically, SerializeAsyncNoAwait should be still faster =)

  • @Misteribel
    @Misteribel Před rokem

    This stuff matters with async sequences or fast async while loops. There are many other differences, and we haven’t yet started on ValueTask…

  • @albatrossherifi2510
    @albatrossherifi2510 Před 3 lety +3

    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.

  • @bruno.arruda
    @bruno.arruda Před 3 lety

    Which theme and/or color scheme are you using in Rider? tks

    • @nickchapsas
      @nickchapsas  Před 3 lety

      I actually have no idea. I think I might have made this one years ago after the VS dark theme

  • @frankroos1167
    @frankroos1167 Před 3 lety

    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?

    • @nickchapsas
      @nickchapsas  Před 3 lety +1

      A Task without they async keyword is still asyncrhonous as long as it is awaited at some point during the pipeline.

    • @frankroos1167
      @frankroos1167 Před 3 lety

      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?

    • @nickchapsas
      @nickchapsas  Před 3 lety +1

      @@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.

    • @frankroos1167
      @frankroos1167 Před 3 lety

      @@nickchapsas I see your point. I'll wait for a video on it. Because it is still an interesting topic.

  • @DanielAWhite27
    @DanielAWhite27 Před 3 lety +1

    I would be wary if you had a synchronization context that would be need to use `.ConfigureAwait(false)`

  • @shuvbhowmickbestin
    @shuvbhowmickbestin Před rokem

    I've been searching for this like everywhere. This is so confusing!

  • @LonliLokli
    @LonliLokli Před 3 lety

    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.

    • @nickchapsas
      @nickchapsas  Před 3 lety

      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.

    • @LonliLokli
      @LonliLokli Před 3 lety

      @@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

    • @UmarO
      @UmarO Před 3 lety

      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

    • @slang25
      @slang25 Před 3 lety

      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

  • @Layarion
    @Layarion Před 2 lety +1

    Well now i have to ask, what is a forward method?

    • @nickchapsas
      @nickchapsas  Před 2 lety

      A forward method is a method that all it really does is expose another method from something that the class has access to

  • @elcharlydev4519
    @elcharlydev4519 Před 2 lety

    Estaria padre un canal en español

  • @granmasterlincoln
    @granmasterlincoln Před 3 lety

    Anyone could get me the David's article he mentions?

    • @nickchapsas
      @nickchapsas  Před 3 lety +1

      github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md

    • @granmasterlincoln
      @granmasterlincoln Před 3 lety

      @@nickchapsas Thank you

  • @vitaliykoritko5080
    @vitaliykoritko5080 Před 3 lety

    I think is better to use Task.Delay in benchmark

    • @nickchapsas
      @nickchapsas  Před 3 lety

      It is actually note because Task.Delay isn’t guaranteed to be precise

  • @omnicoinv2
    @omnicoinv2 Před 3 lety +1

    Before watching comment: my understanding is that the runtime/compiler kinda handles any redundant awaits so both styles have the same behavior

    • @nickchapsas
      @nickchapsas  Před 3 lety +7

      This comment is awesome because it shows that there was a need for this video

  • @asedtf
    @asedtf Před 2 lety

    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.

  • @ianmarteens
    @ianmarteens Před rokem

    So, basically, wrapping the call inside a try/catch would have achieved the same… without all the state machinery.

  • @der.Schtefan
    @der.Schtefan Před rokem

    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.

    • @nickchapsas
      @nickchapsas  Před rokem

      My database requests in production take less than 1 ms

  • @SkyyySi
    @SkyyySi Před 2 lety

    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

  • @semen083
    @semen083 Před 3 lety

    async Task Video()
    {
    await VideoTalksStream();
    System.Console.WriteLine("See you in the next video");
    await Task.Delay(1000);
    System.Console.WriteLine("Keep Coding");
    }

    • @kano636
      @kano636 Před 3 lety

      You need a return Task.CompletedTask;

    • @semen083
      @semen083 Před 3 lety

      @@kano636 why?

    • @kano636
      @kano636 Před 3 lety

      Good practices... I tend to use it always.

    • @semen083
      @semen083 Před 3 lety

      @@kano636 what the difference in functionality between return completed task and not return?

    • @kano636
      @kano636 Před 3 lety

      @@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

  • @PticostaricaGS
    @PticostaricaGS Před 3 lety

    When using tasks our rule is to always use async Task and await, that prevents any issue of non-awaited calls

  • @GammerAdam
    @GammerAdam Před 3 lety

    Great video but please do not fall for the "Weird face thumbnails are funky" trend. Please?

    • @nickchapsas
      @nickchapsas  Před 3 lety

      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

    • @GammerAdam
      @GammerAdam Před 3 lety

      @@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.

  • @briankarcher8338
    @briankarcher8338 Před 3 lety

    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.

  • @AleyCZ
    @AleyCZ Před rokem

    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.

  • @mbalaganskiy
    @mbalaganskiy Před rokem

    CZcams is a developer's nightmare - everyone wants to do videos instead of good ol articles which you can skim in 1 minute :)

  • @Valentyn90A
    @Valentyn90A Před 2 lety

    Of course it's slower, lol, but that's not the point.

  • @KoScosss
    @KoScosss Před 3 lety

    Wish there would be a benchmark comparison with ValueTask’s hot path.

    • @nickchapsas
      @nickchapsas  Před 3 lety +2

      8th of April is the scheduled release date for the ValueTask video