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

Поделиться
HTML-код
  • Опубликовано: 27 ноя 2024

Комментарии • 124

  • @ElSnakeObwb
    @ElSnakeObwb 3 года назад +54

    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  3 года назад +11

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

    • @EMWMIKE
      @EMWMIKE 2 года назад +2

      How will it get disposed earlier. Dont understand

    • @vifvrTtb0vmFtbyrM_Q
      @vifvrTtb0vmFtbyrM_Q 2 года назад +2

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

    • @sc12sc
      @sc12sc 2 года назад

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

    • @saltukkos
      @saltukkos Год назад

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

  • @Otto-the-Autopilot
    @Otto-the-Autopilot 3 года назад +38

    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 3 года назад +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).

  • @englishmasterycorner
    @englishmasterycorner 3 года назад +23

    Interesting results. Love these benchmarking videos.

  • @helicalius
    @helicalius 3 года назад +5

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

  • @willinton06
    @willinton06 3 года назад +20

    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

  • @Lior_Banai
    @Lior_Banai 3 года назад +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

  • @sohampatel1063
    @sohampatel1063 3 года назад +12

    Content factory.☺️
    Better than best.☺️

  • @IAmFeO2x
    @IAmFeO2x 3 года назад +29

    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  3 года назад +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 3 года назад +10

      @@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  3 года назад +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 3 года назад +4

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

    • @nickchapsas
      @nickchapsas  3 года назад +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!

  • @chrisd961
    @chrisd961 3 года назад +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! 😆

  • @TuxCommander
    @TuxCommander 3 года назад

    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.

  • @aj.arunkumar
    @aj.arunkumar 2 года назад

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

  • @benjamininkorea7016
    @benjamininkorea7016 Год назад +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?

  • @lexer_
    @lexer_ 2 года назад

    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.

  • @ilyakurmaz
    @ilyakurmaz 3 года назад

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

  • @alphaios7763
    @alphaios7763 3 года назад +5

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

  • @МихаилКожевников-ъ2ь

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

  • @stuzyx906
    @stuzyx906 3 года назад

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

  • @chriscardwell3020
    @chriscardwell3020 3 года назад

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

  • @evanboltsis
    @evanboltsis 3 года назад

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

  • @albatrossherifi2510
    @albatrossherifi2510 3 года назад +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.

  • @alexpablo90
    @alexpablo90 3 года назад

    Nice video. I love this benchmarking videos.

  • @Rolandtheking
    @Rolandtheking 3 года назад

    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  3 года назад

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

  • @josephizang6187
    @josephizang6187 3 года назад +3

    Do you do mentorships? You are awesome!

  • @0shii
    @0shii 3 года назад +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  3 года назад +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.

  • @Bundynational
    @Bundynational 3 года назад

    @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  3 года назад +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.

  • @Misteribel
    @Misteribel 2 года назад

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

  • @vasiliyfofanov2605
    @vasiliyfofanov2605 3 года назад

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

  • @efrenb5
    @efrenb5 2 года назад

    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.

  • @ayudakov
    @ayudakov 2 года назад

    Thank you!

  • @navrim
    @navrim 2 года назад

    you make such good content

  • @ogy23
    @ogy23 Год назад

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

  • @michaelameyaw1746
    @michaelameyaw1746 3 года назад

    I got something new here. Thanks

  • @kenjacobi9154
    @kenjacobi9154 3 года назад

    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.

  • @powerclan1910
    @powerclan1910 2 года назад

    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

  • @RoiTrigerman
    @RoiTrigerman 3 года назад

    Great video, thanks!

  • @ValueLevit
    @ValueLevit 3 года назад

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

  • @kostasgkoutis8534
    @kostasgkoutis8534 3 года назад

    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?

  • @InshuMussu
    @InshuMussu 6 месяцев назад

    Amazing.. thanks

  • @harag9
    @harag9 3 года назад

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

  • @shuvbhowmickbestin
    @shuvbhowmickbestin Год назад

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

  • @sasukesarutobi3862
    @sasukesarutobi3862 3 года назад

    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  3 года назад +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 3 года назад

      @@nickchapsas Thank you for clarifying.

  • @BK-19
    @BK-19 3 года назад

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

  • @vivekgowda1576
    @vivekgowda1576 3 года назад

    Nice 😊...love it

  • @superpcstation
    @superpcstation 3 года назад

    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  3 года назад

      I think I actually address this in a video I made a while ago about David's guidelines: ruclips.net/video/lQu-eBIIh-w/видео.html

    • @superpcstation
      @superpcstation 3 года назад

      @@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  3 года назад

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

  • @ARS-fi5dp
    @ARS-fi5dp 2 года назад

    Thanks man

  • @Layarion
    @Layarion 3 года назад +1

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

    • @nickchapsas
      @nickchapsas  3 года назад

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

  • @iGexogen
    @iGexogen 3 года назад

    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  3 года назад +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.

  • @wilbit
    @wilbit Год назад

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

  • @antonmartyniuk
    @antonmartyniuk 3 года назад

    Cool video!

  • @nklaveren
    @nklaveren Год назад

    Interesting 🎉

  • @DanielAWhite27
    @DanielAWhite27 3 года назад +1

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

  • @bruno.arruda
    @bruno.arruda 3 года назад

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

    • @nickchapsas
      @nickchapsas  3 года назад

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

  • @roflex2
    @roflex2 3 года назад +1

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

  • @frankroos1167
    @frankroos1167 3 года назад

    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  3 года назад +1

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

    • @frankroos1167
      @frankroos1167 3 года назад

      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  3 года назад +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 3 года назад

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

  • @omnicoinv2
    @omnicoinv2 3 года назад +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  3 года назад +7

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

  • @KoScosss
    @KoScosss 3 года назад

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

    • @nickchapsas
      @nickchapsas  3 года назад +2

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

  • @LonliLokli
    @LonliLokli 3 года назад

    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  3 года назад

      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 3 года назад

      @@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 3 года назад

      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 3 года назад

      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

  • @vitaliykoritko5080
    @vitaliykoritko5080 3 года назад

    I think is better to use Task.Delay in benchmark

    • @nickchapsas
      @nickchapsas  3 года назад

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

  • @der.Schtefan
    @der.Schtefan 2 года назад

    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  2 года назад

      My database requests in production take less than 1 ms

  • @AvenDonn
    @AvenDonn 3 года назад

    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.

  • @SkyyySi
    @SkyyySi 2 года назад

    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

  • @ianmarteens
    @ianmarteens 2 года назад

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

  • @elcharlydev4519
    @elcharlydev4519 3 года назад

    Estaria padre un canal en español

  • @lincolntx98
    @lincolntx98 3 года назад

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

    • @nickchapsas
      @nickchapsas  3 года назад +1

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

    • @lincolntx98
      @lincolntx98 3 года назад

      @@nickchapsas Thank you

  • @mbalaganskiy
    @mbalaganskiy 2 года назад

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

  • @GammerAdam
    @GammerAdam 3 года назад

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

    • @nickchapsas
      @nickchapsas  3 года назад

      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 3 года назад

      @@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 3 года назад

    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 2 года назад

    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.

  • @PticostaricaGS
    @PticostaricaGS 3 года назад

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

  • @Valentyn90A
    @Valentyn90A 2 года назад

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