8 await async mistakes that you SHOULD avoid in .NET

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody i'm nick and today i want to talk about eight await async mistakes that you should avoid in dot net and dotnet coin general now i want to talk about what a weight async is there are many videos that are talking about the same subject and they cover what is in depth my favorite one personally is one from raw coding i'm going to leave a link in the description down below but you can watch that if you want to understand how a way they think works and what it is in this video we're going to talk about examples that you might see in your everyday code and talk about what you should and shouldn't do and this is based off a repo from david fowler who is a partner software architect at microsoft and he's the guy who made singular r i think he also made kudu and you get ho so he's a guy who really knows this stuff and i'm gonna make a video form of that uh page and i'm gonna leave a link in the description of that page as well if you want to read that it goes through some extra stuff while i compiled what i think are the functional things that you're going to see in a day-to-day basis if you're going to go to a specific topic you can use the timestamps in the description down below so without any further ado let's go straight to the video this video is part of my general asp donor core series so if you don't want to miss any episodes please subscribe with the simplification belt to get notified when i upload a new video so what i have here is a solution with a bunch of projects and each project has one of the use cases i want to show so we're going to start with the first one which is called once async always async and what this means is that once your project has an async call and you're awaiting something you should await all the way to that call it's usually going to end up in a network call or an io call of some sort like a database call or a websocket or something but once you have that and you can await it await it all the way so in this example i have a void main method that will call a repository that has an async method and when you call that async method gives you back a result now here i'm synchronizing the call because it might not go through a network call or something but really there is a way that i can actually await this all the way and it is to change my async main to an async task now this could be an endpoint in a controller or an event that happened in wpf application or that sort of thing but there is pretty much always a way that you can start this async flow from top to bottom so in this scenario we just properly await and we change this tracing task if it was a controller it would be an iasync result and you'd want to make that an async task of iasync result now the next one i want to talk about and i have covered this in a different video as well is that async void is bad so what i have here is just a random like repository that returns some value it doesn't have any purpose here and in reality this isn't really doing anything other than just returning one but what i want to show you is in the controller in this project what i have this is the diamond controller that comes out of the box with dotnet core i have an async void of some background thing that i want to run and if i actually run this project let me show you how this behaves i'm going to call the end point in postman and as you can see i get the response and after a while the number is one this happened with a two or like five seconds delay i think it's two seconds yeah so we waited for two seconds here to do some background tasks the task here is just print something in the console in your case might be sent an email a sent a notification that sort of thing now the thing is you shouldn't really be using async void for this thing and the reason for that is actually quite interesting if for some reason this thing throws an exception and let's say that the email failing mail failed and there's an exception here let me rerun this real quick i want you to see what will happen so running the endpoint again i got my response but i got a fatal exception as you can see my compiler broke and if i run this through the process is killed the thing is not running anymore i didn't stop it but an exception in an async void that's running as a background thing will actually cause the whole process to crash you might not have seen this or your application might have crashed and you don't know why it has happened but you really shouldn't be doing this there is a way around this and the way around it is to use task.run which in this code i'm going to change this again to an async task here and what i can do is i can simply say image just copy this task dot run and we're gonna paste the method name and now that's it so let me just put back the exception here so throw new theo is not a word throw new exception and some sort of text and if i debug this and run it again you will see that now the application will not throw but it will also want die and the reason why it won't throw is because it is in a fire and forget task.run there is a way to change this so it throws an unobserved task exception but i feel like this is not part of this video so i'm going to leave this outside but but you could normally if you want to handle it yourself if you expect something to happen here you could very much do this you could wrap it in a try catch and if you restart the application and you hit the end point again you will see it properly thrown here now as you can see it is so task.run is its own beast and we're going to cover a few of the use cases in the next project but this is all about async voids and how you should not be using them so change it to an async task so the third one is called prefer from result over run for precomputed stuff so what i have here is some service that is getting some value it's doing some stuff in there and it's returning a new number and if i take a look in that method you'll see that all it's really doing is it's multiplying it by two but i've wrapped that to and tested run because there might be some amazing flow that i need to follow so i have a effectively what is a trivial thing that i want to run in an async fashion now the problem with that is task.run will waste a thread pull thread and will put it back into the pool without really doing anything with it so you're wasting resources what you want to do instead in that scenario is you want to do task dot from result and you don't need this you can simply do that and this won't actually waste any threads from the thread pool it's way more efficient and you could actually take this step further because in c sharp 7.3 i think i could be wrong on this and value task was added and value task is a bit of a different thing than a simple task and the reason why we're going to use value task is because while task is extract and it won't actually allocate any object on the managed heap and in order for that to actually be a thing i need to copy that i need to take this and i'm just going to put it here and it's another result so this is also more memory efficient other than just more resources efficient in terms of a thread pull so this is a better way to do it now however there is some fine print in using value task um i also won't cover this on this video i'm going to link the page for value tasks in description down below to read more about it but just know that for trivial pre-computed stuff it's better than using task.run the fourth one is called sync over async i have a program.cs here and what i'm doing is because i have a void method and i have an async call i'm doing a dot result to synchronize this call the problem with this you might hear this a lot is that it can cause deadlocks however dotnet core fixes this because donor core doesn't actually have a synchronization context which is what was causing that to begin with so it will work the problem with it isn't just that it's gonna cause deadlock and by the way it's it's not just dot result it's also dot weight uh this can also uh do the same thing so it's not the synchronization context and the deadlock that is bad it's that it will result in the use of two threads instead of one to compute synchronous operations so again potential threat preservation and wasting resources so if you have something like this you really wanna await it some people might say what happens if you cannot await something well realistically in any dot net scenario modern dot net scenario there is no way that you cannot await a call pretty much every work everything can start from an async scenario an async task and you can keep awaiting them all the way down to the final call there might be a use case that you are thinking right now and saying hey what's happening and that's the constructor and we're gonna cover that in one of the next projects so the next one is prefer await over continue with i have a program.cs here which has an async call and then i'm doing continue with i'm getting the task i'm getting the result and i'm adding 2 to the number so i'm using continue with here to sort of chain something with the result of this on something else and i've seen this quite a lot and some people also use it for fire and forget stuff i can understand that but the thing is never use it like this and to be honest i think david fowler would argue that you should never use it in general but i digress what you want to do instead is you don't want to do this you want to get the um final number let's say which is the number you get from an async call plus 2. you might be thinking hey nobody does that well actually people do that so this is why i included it and you might have a very niche case for continue with but i've never personally seen it and i've seen a lot of code be used in a way that using a weight async doesn't make more sense there's also some fine print about capturing the correct synchronization context but like we said i don't record works differently so it doesn't really affect that but if you use continue with i think your code also looks cleaner because you don't have this nesting of chaining the calls it looks like a fluent api but it doesn't really look like it when you read the code from top to bottom so yeah don't really use continue with if you can and the next one is called always pass cancellation token now i cannot tell you how many times i've seen this and people question its usage and its functionality and why you need it but it's actually great so in case you didn't know that pretty much every controller method actually not pretty much every control method can also have a cancellation token parameter and this is automatically populated from dotnet core and you can use that and you can pass it down to effectively what will be an i o call potentially a network call to an http client or it could be a database called a sql call any sort of io call i think even file io can actually use it and the reason why it's used because dotnet core will notify you when the request is cancelled and a request can be cancelled and let me show you that i mean i'll show you this when i run this but what i have here is i'm passing down the cancellation token and even though i don't have an i o call i have a delay of five seconds with the cancellation token and then i have an asynchronous task.run which i'm awaiting and let me show you what happens if i cancel without the cancellation token so if i don't use it right so let's do this so we don't use it at all so let's file the request and i'm gonna cancel it i cancel the request and as you will see now wait for five seconds the thing after these five seconds delay still happened i wasted my resources i called the database even though the user said i don't want to see this data and you can have the scenario with people constantly refreshing a page or just pressing the x button on the browser or just people leaving their website that started you know a bunch of api calls as they entered so all that can actually be caught and the cancellation token will be triggered and let me just put it back stop this and rerun it and show you what happens now so if i do that and i run the request and i cancel it now i instantly get this exception but as you can see no message saying wait five seconds no nothing this threw a task cancelled exception and it says a task was cancelled which is what i'm expecting when i'm cancelling cancellation token in this context if you were using a true io call that goes to a database then the library would actually be handling this and canceling it silently to simulate that what i can do is i can run this and i can get the name of the exception here so task cancel exception and say that i don't really want to act on it let's just silently uh cancel it and let's return and an empty new world contact so enumerable dot empty of that type you can handle this all anytime any way you want it doesn't really matter i just want to show you what happens when you actually cancel it and handle it gracefully so i can go back here i can press get and if i let this run it's going to wait five seconds and give me back the data so let's see how the happy path is working as you can see wait five seconds got the data back if i started and cancel it i didn't get any data back and it doesn't say wait for five seconds twice so it's gracefully canceling the request without actually throwing any exceptions it is throwing it but it's cutting it silently so please pass the cancellation token all the way down that's the whole point of this because it will save resources you won't need to waste time computing any maths or any model or any model mapping or any io calls after a cancelled request please please please use it so the seventh one is called prefer async over task so what do i mean by that you might have seen this where there's a some sort of i o call now imagine this is calling a database and it's not just getting one and people have a task and they return a task uh without actually having the async and the weight here now there is a valid case for it i'm not saying you shouldn't definitely do it but you should know exactly what's happening when you're using it so when you're using a weight async the asynchronous and synchronous exceptions are actually normalized to be asynchronous the code is also easier to modify because you might be seeing this um you know small thing here but in a bigger method with more io calls having these tasks floating around and just returning the task without actually awaiting them can be a bigger pain last but not least exceptions thrown in here will be automatically wrapped in the return task instead of surpassing the caller with an actual exception so debugging is better exceptions are also better and the code is easier to modify also imagine using a the using keyword in here it would be a mess so there is that now please note here that using task and async task and asking a weight and using the state machine that is built behind the scenes will always have some performance hit the performance consideration should actually come into picture when you really need those million microseconds really or even nanoseconds uh that you might be wasting building the state machine and going through the state machine but again this is a topic for a different time i don't want to go that in depth and talk about exactly how the state machine works because this will be a long video and last but not least probably the most important one and the one that i've seen being done wrong everywhere pretty much is async in constructors so as you can see we have a connection factory which we're passing into some service and we're getting some service back and this connection factory is creating some sql server connection let's say so in here you really don't need to know what's going on in that so this service requires a my connection can be a database connection let's say and what i'm doing here is because i need to create the connection to the database so i don't have latency in the beginning what i'm doing is i'm doing a dot result to start the connection now this will synchronize the call which is a really bad practice as we mentioned already we don't really want to do that it has the same threshold and deadlock scenarios as we mentioned before and this could be a singleton so it's no biggie but if this was a scoped or a transient dependency and this was happening on every call you could really go to a thread conservation scenario so what you want to do instead is you want to change a little bit and play with the factory pattern so you want to make this private and you want to inject the actual connection instead of the factory here so normal stuff my connection equals my connection and by making this private as you can see in my program.cs i can no longer do a new service and pass the factory and that's not because it's a the factory is not what it's requiring it's because the constructor is not accessible so it won't allow me to do that what you want to do instead is you want to say public async or static async task and now we can have that and we're gonna return itself so some service and we're gonna say create async and now we have the dependency that we had before which is the uh my connection factory connection factory yes connection factory let's just call it factory and now in here i can actually have all the async i want so return new some service that has the factory dot connect async and we're gonna await it so properly awaiting the connection opening call and go back to the program now i'm gonna change this to an async task because i can and i'm going to say some service dot create async and i'm going to pass my factory and now my whole flow is async and i can properly await this so that's how you deal with asynchrony in the constructor now in general if you need to have logic in the constructor or async calls you might be doing something wrong but this database opening scenario is something i can totally get behind some people do this by having normal service registration and then calling open async or create async um in their di just to kickstart it but i prefer this method it's more clear you know how the thing is built and you can pretty much do anything dependency injection will become different because you will need to register this method in your di container instead of what you would do normally which is the constructor but it still works that's all i have for you for this video thank you very much for watching special thanks to my github sponsors for making these videos possible if you want to support me as well you're going to find a link in the description down below leave a like if you like this video subscribe for more content like this and ring the bell as well and i'll see you in the next video keep coding
Info
Channel: Nick Chapsas
Views: 141,365
Rating: 4.9079838 out of 5
Keywords: Elfocrash, elfo, coding, asp.net, .netcore, dot net, core, C#, how to code, tutorial, asp.net core, csharp, lesson, microsoft, microsoft mvp, .net core, nick chapsas, chapsas, asp.net core 3, .net core for beginners, await async, await async best practices, await, async, 8 await async mistakes that you should avoid in .NET, await async mistakes, asynchronous, thread, threading, async Task, Task, C# Task, C# async, dotnet, .net
Id: lQu-eBIIh-w
Channel Id: undefined
Length: 21min 13sec (1273 seconds)
Published: Tue Sep 08 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.