Settling the Biggest Await Async Debate in .NET

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody I'm Nick and in this video I'm going to try and set in one of the biggest debates of a white async in c-sharp if not the biggest really and that is the following assume we have these two methods now both of them can or may not do some stuff before eventually they have to return something that returns a task either of some type or just a task itself do you turn this method into an async task of that type and I'll wait it or do you just return the method and have something Upstream eventually awaited because it has to be a way to return something the pros and cons in both cases I'm going to try and see both sides of a coin and see which one you should be using because ultimately depends on what you need in your code base if you lack of content and you want to see more make you subscribers in the certification Bell and for more training check out nickjobses.com and very quickly before I move on I want to remind you that I'm still running my two-day in-person workshop from Zero to Hero effective testing in c-sharp in a bunch of conferences here for now the confirmed ones are ndclondon. Mania NDC also an NDC portal with NDC Copenhagen being announced soon and more to be announced in those two days I'm taking you from the basics of unit testing going into mutation testing and now we're following it up with integration testing we're building on top of that knowledge and ending it with performance testing it's an exciting two days where I will start from a basic and we're going to some pretty advanced stuff where you have things you can take away and apply to your day-to-day job speak with a manager I hope to see you there now before I dive into the code I actually want to take a step back and talk about the two different mindsets that you can have looking into this problem the first one we're going to call the David Fowler mindset now David Fowler has actually written a very descriptive GitHub page where he talks about multiple asp.net core diagnostic scenarios and it has this whole asynchronous programming thing about things you should and you should not do and I've actually made a video on this like two years ago but one of the things he talks about is prefer async or weight over directly returning the task so he says you should act actually have async await here even if the only thing you're doing in this example is just forwarding the method now he does point out some benefits of this approach for example that the code is easier to modify and what would happen if you added using into the mixer we're going to look into that but then at the bottom he says that note there are performance considerations when using async State machine over directly returning the task but that's basically the only con he actually outlines now obviously David is a very smart guy he works of the Aspen core team but there's another super smart guy called Kevin Goose that you may or may not know but if you have followed his blog or his NDC talks or talks around the world he's a super smart guy working for datadog and he's the type of guy who actually finds bugs in the CLR itself and he diagnoses them and he tries to fix them so he really really really knows his.net incredibly smart individual really good in-depth talks I highly recommend you check him out and what people ask me how do I know what I know and how am I learning Kevin is one of the people I actually follow and Kevin says in this performance best practices in c-sharp blog that out of habit or muscle memory you might ride async await here in this client example which is the same example I had in my code but what he recommends is don't just return the task directly do not have that async or white now he points out some cases where this might be impossible to do or incorrect but the recommendation is if you care about performance just directly return the thing don't have that a single weight so those are the two comes of thinking I'm going to follow and try to see pros and cons in both cases in my examples now there's two things we need to take a look here functionality and behavior and then performance I'm gonna start with performance just to get that out of the way and then we're gonna go into functionality and behavior we're going to start by adding benchmark.net in this project and thankfully I won't have to write all the benchmarks from scratch I actually have them ready I'm gonna import them in this code base and I'm going to talk you through what each Benchmark does alright so the two batch my classes are in and what I'm going to do here is I'm actually going to use tasks that are all in memory because the only thing I care is the Delta of the state machine itself I don't really care about anything else I just care about the cost of having a state machine generated and being traversed now later in this video I will show you something that does real work and see how that compares against just this email results but as you can see here all I'm having is a static read-only task of integer a value type and I also have the same thing but with a reference type just to see if there's any difference between the two types now in those benchmarks as you can see I have those methods that have or don't have the async or weight as core methods private methods and then the two benchmarks are the ones that actually both use async or weight and the reason for that is to make sure that they're both computed by the time that the Benchmark actually runs because if I do it on this level you know is the task at this point computed or not we don't know so I just want to be 100 sure and we can calculate the Delta still in these two approach watches so what I'm going to do in program.cs is say Benchmark Runner dot run and we're going to start with the valve benchmarks in fact you know what let's run both of them so I'm just gonna say here an array I'm going to say type of Val benchmarks and then type of ref that's it let's turn this into release mode and have it run and let's see what we get in the end all right so results we're back and this is what we have here first with the value type benchmarks so as you can see the one that does have a weight async is eight nanoseconds slower than the other one but still we have no memory allocation in this case now for the reference types and sorry for this being inverted this is the one with a weight I think as you can see it is around 13 and a half nanoseconds slower and it does have a bit of memory allocation on the Heap so there is a visible difference now how much does this mean in reality well that depends on what you're doing with your application but that's how much we can observe now how much is that really when your system is doing some real outweight async work for example reading a file from the disk or maybe reading a file over the network well I just added a new Benchmark over here called file benchmarks does effectively the same thing as before but now it uses the read all bytes async to read this example Json file and just return the byte array back we have a real method now with real away tasting semantics and we return some data back so what I'm going to do here is actually go to The Benchmark and run this one instead so delete all that and run file benchmarks and see what's the Delta there now all right so results are back and let's see what we have here so as you can see here the task with the weight I think 55.57 microseconds and 1.25 kilobytes of memory while the one without a weight async 55.21 milliseconds so a bit faster and a bit more memory efficient now obviously these scales but this is technically an observable difference especially the memory location so take it for whatever it is in your use case and always be measuring so now they have all the performance stuff out of the way let's talk about the functional aspect of the approaches because there are good points in both camps now obviously the biggest Point towards not using it is that your code is simpler you don't need to have a single weight you don't need to avoid anything all you say is just return that task so arguably less code equals cleaner code okay I get that but then David comes and points out that if you have an exceptional throw on something like this that is not awaited and it does not have the async word keyword as opposed to this approach then exception messages will actually look different here what I'm going to do is I'm going to delete all of Benchmark stuff and I'm going to create a new David class over here and I'm going to do the following once I'm going to await the get Nick method that doesn't have the async of weight stuff and then I'm going to await the getnik async method that does have it the reason why I do that is because I want to compare how the exceptions will differ now in this request I do do note there will be an exception and it's going to be a 403 Forbidden because I haven't actually authenticated against GitHub to call their API to get that data so I'm going to go ahead and just run this and I'm going to be still in release mode and let's see what we have here I'm going to open a blank diff window here in writer and first I'm gonna put what I have on the left and then I'm gonna put over here what I have on the right and as you might have seen they actually differ a bit so as you can see over here the thing that does have a weight async actually indicates that the error was thrown in that method while the one that doesn't have a way they sync omits that so ideally if you do use the way they sing you get a more accurate stack Trace you know exactly what happened and where oh line 14 here while here the line is line 8 in the program.cs and if I go here where one says that the error actually happened here when in reality the other one says that the error happened here which is true so that is a good point you will get a better more accurate stack Trace now I'm the other hand Kevin points out that what if you have something like this here that is disposable and you want to have a using keyword a using statement here and you want to dispose it after you used it well if you actually do it like this in the one that doesn't have a way they think then you're done for because you actually don't know if this call has finished by the time that this is disposed in fact it most likely wouldn't have finished and that is because if we go into the lowered code for this code as you can see over here it just Returns the task but then dispose the client but we don't know if the task has returned by the time that client is disposed so this could actually create a bug and throw an HTTP client disposed exception and this can happen with anything that is disposable by the way while the other approach as you can see over here with the away they sync will generate the state machine over here and then it's going to go through all the stages and then in the end it's going to call the dispose method of that client after it's finished doing its thing so that approach here is just completely Incorrect and you should not be doing it now David does point out some other stuff in his notes for example debugging hangs I never had that happen to me I don't know if it's the writer debugger or something else but out of all the other points these two are pointed out is in my opinion the most important one you should be aware of and actually what I want to show you before I show you the state machine itself is that in the HTTP client itself that we have been seeing all this time async or white isn't used everywhere where you have something like this which is called a forward method there is no point in wasting that performance because if something fails you want to know that it failed when it was eventually called so even here it is not used it's only used when we go to the get string async core because that's the thing that does all the magic in here await stuff in the middle of the method so you wouldn't be able to omit it Anyway by the way since I just saw it let's the comment down below letting me know if you want me to make a video on configure await false and when you should be using it so as you can see you can see from Microsoft's code it really depends and in my opinion when I see followed methods like this I never use it just because if you have a forward method that calls another one that calls another one you have the stacking effect of all these State machines that you really really really don't need so even though I was using it in the beginning I am now omitting it for all the places that it does make sense to omitted given all those things that I did point out already in this video now since you are here you might be curious I want to show you how this code that has a weight async differs from this code that doesn't so again if I compile and I go to the IL viewer then you're gonna see in the low level C sharp that this one that doesn't have anything and it just Returns the task will stay the same and just return the task itself and hope for something Upstream to avoid it so it can actually return the value but if I click on this one that does have a way this thing as you can see over here then we have a bunch of stuff going on and what I'm going to do is actually just copy all that and show you how the code code changes so in contrast what's going to happen when the compiler sees this method over here with async and await is actually drop this state machine this struct state machine which by the way will be struck in release mode to make it more efficient but a class in debug mode to make it easier to debug and it's going to look exactly like this and that C sharp generated by the compiler actually compiles for our code as well and then this method is going to have the async and await instructions removed and it's going to look like this where it's the exact same name but now it is of type task int it doesn't have a way to sync anymore it has the state machine here it has a bunch of stuff created like the initial State of the State machine and the asymptask method we are created and then the state machine is passed over here we start the process and eventually it Returns the task and this is decorated with the async state machine and this type of to indicate I guess the state machine responsible for this method and in case you're wondering yes this would actually work if I just call it as it is so I can go over here and I can say just return so I don't have to worry about deleting anything and I'm going to say VAR Val benchmarks equals New World benchmarks and then result equals and I'm gonna actually turn this into public because I think it is private so I'm just going to quickly do that and say oh wait yes I can await it and just return valve benchmarks dot that I'm gonna quickly add the console.writeline and put the result here which should be one if you remember so I'm gonna quickly just run this and let's see what we get back so as you can see one yes it will follow all those steps because effectively that is what a wait a thing is preventing you from writing it's just way smarter in each use case and it adapts based on their waiters and a bunch of other stuff which I'm not going to talk about in this video it's complicated so to recap which ones should you use well considering you care about performance and also about debuggability readability and maintainability then you want a healthy medium where in most cases in my opinion you shouldn't use it and also you shouldn't use it when it causes issues like the one with a disposable or try cats which can cause problems but if you have a bunch of forward methods that don't use and then eventually you do surface one public method and you might want to use it there so really it depends but now you know everything you need to know to make that decision for yourself but now I want to know from you which one have you been using for all those years do you have a preference or do you just choose based on your use case leave a comment down below and let me know well that's all I have for you first video thank you very much for watching special effects to my patreons make videos possible if you want to support me it's really gonna find the link in description down below leave a like if you like this video subscribe multi like this in the Bell as well and I'll see you in the next video keep coding
Info
Channel: Nick Chapsas
Views: 130,877
Rating: undefined out of 5
Keywords: Elfocrash, elfo, coding, .netcore, dot net, core, C#, how to code, tutorial, development, software engineering, microsoft, microsoft mvp, .net core, nick chapsas, chapsas, dotnet, .net, .net 7, .net 8, async await, async, await, async .net, async c#, async await .net, async await c#, Settling the biggest await async debate in .NET
Id: aaC16Fv2zes
Channel Id: undefined
Length: 14min 46sec (886 seconds)
Published: Thu Dec 22 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.