JeremyBytes - Task and Await in C# - Part 2: Basic Exception Handling

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
this is Jeremy Clarke of Jeremy Bates calm and today we're going to continue our series on tasks and a wait-and-see sharp this time we'll be taking a look at basic exception handling when we're using task or a wait as a reminder we're working with the task asynchronous pattern this is task based and a task represents a concurrent operation now once we start talking about exceptions happening on other threads things start to get interesting so we'll see how we can handle that using tasks directly and last time we saw how the await operator can help us hide a lot of this complexity and we'll see that exception handling with a wait is pretty straightforward but we do get some more power if we use tasks directly so our focus should really be picking the right tool for the job so let's flip over to our code and get going with this so here we are in Visual Studio and as a reminder here's the asynchronous method that we're consuming now the interesting part is this does return a task of list of person so this represents a task that will ultimately return to us a list a person as a result and we saw how to consume this method two different ways we use the task directly and in that case we created a continuation the continuation runs after the task is completed and we did have to add the task scheduler dot from current synchronization context to make sure that our continuation ran on our UI thread that's because we're interacting with the UI elements directly here using the await operator is quite a bit more straightforward in fact it looks just like synchronous code the only difference is we have the await operator before we call the get method the result is that it will pause this method until that task returns and then it will pick up from where it left off and if we run our application we'll see both of these have the same result so if I click on fetch data with tasks I do have that three-second delay we have to wait for and then our data is populated and we see the same thing if I click the fetch data with a wait button now before moving on to exception handling I want to point out a small problem that we have with our UI because our methods are asynchronous that means our UI stays responsive while we're waiting for our task to complete but there's a problem with that because if I click the task button and then click the task button again before it comes back notice that we just got two results sets crammed into our UI and the exact same thing is true if we use our wait button so if I click it and then click it again before the results have come back we'll see that first set comes back and then we get another set of results now why do we have this problem to figure this out we need to look at where we call clear list box notice that it's at the top of both of our button click methods this means that when I click the button it will immediately clear the list box then if I click the button again before any data has come back it will clear the list box again but then because of the asynchronous nature of our code our data starts to return so that first data set will come back and then that second dataset will come back and our methods are happy to continue from where they left off so this is why we end up with multiple data sets inside of our output box now let's go ahead and fix this and to do this I'm just going to update our UI a little bit and let's do our oh wait button first so all I'm going to do is take our fetch with a wait button and set its is enabled property to false at the top of this method so this will disable the buttons so that I can't click it again and then at the bottom of the method we'll go ahead and re-enable it by setting it back to true and then if we run our application will see that our problem is solved once I click the button it's now disabled so that I cannot click it again and then once our data comes back it rien a Buhl's so let's go ahead and watch that again I'll click it our button is disabled and then it's renamed once our data comes back now this isn't the best code at this point because what happens if an exception or other problem happens in the middle of this method well our button will not be re-enabled so to make sure that our button is always renamed old I'm going to go ahead and wrap this in a try finally block now I love code snippets and there's a great keyboard shortcut ctrl a ctrl s for surround with so I highlighted the block of code pressed ctrl KS and now I'm going to type in try F which is our snippet for a try finally block and now when I hit enter we see that the block of code that I had highlighted is now wrapped in a try now I will need to move the part where we're renaming our button into the finally block and then this will get us the behavior that we want so at the top of the method our button is disabled then we run the main body of our code and then we re-enable the button at the end and again if something goes wrong in the middle we still re-enable the button let's just verify that this still works so again I click the button it's disabled so I can't click it again and then it's renamed once the data returns so let's do the same thing with our task method so up here in our task at the very top of the method we'll just disable the button so this will be the fetch with tasks button is enabled equals false and then I might be tempted to re-enable it down here at the bottom of the method by setting it back to true but if we do that we won't get the results that we expect in fact if I click the button it just renate immediately and the reason for that is that we do have that asynchronous portion in the middle of our method so really what I need to do is I need to put this inside the continuation that I have so we'll go ahead and put this inside or continue with method so that this will run after we populate our list box and so now if we do this and click the button will see our button is disabled and then after our data comes back its renamed now we do have the same problem with this block of code what happens if we get an exception somewhere in here before we can re-enable our button well to explore this further we'll look at actual exception handling when we're dealing with these asynchronous methods so let's cause an exception so inside of my get method after I do my task delay I'm going to throw a new not implemented exception and we'll say get method is not implemented now I'm doing this after the delay so that we can still see our asynchronous method running and then the exception will happen after that pause and before we make any changes to our client code let's go ahead and see what the current behavior is and again we'll start with the await button so we'll go ahead and click on fetch data with a wait and then we'll wait our three seconds and then what we'll see is we have an unhandled exception in our code and if we look at the additional information we see it says get method is not implemented the actual method from the exception that we threw now this code is pretty easy to fix in fact I already have the try block which means I just need to add a catch block to this too and in this case I'm just going to catch exception I won't catch that specific not implemented exception and as we can see exception is not coming up that's because I haven't added the system namespace in my using statement but notice the little purple underlined under the e that's Visual Studio offering to help me out so if I press control dot we'll see that one of the options is using system so I just hit enter there and it adds that using statements at the top of my class for me so inside our block well just do a message box show and we'll show the exception message and I'm also going to give it a caption of exception that's because later on we will be dealing with cancellation and I want to make sure we know which message boxes are popping up so with our catch block in place let's go ahead and run our application again we'll click on our wait button and we'll see we do get a message box that pops up that says get method is not implemented now one other thing to note is that our fetch data button is still disabled that's because this is a modal dialog and so we haven't run that finally block yet in our method once I click OK that finally block will run and our button will re-enable so we've seen by using the await operator that our code is pretty easy to work with in fact this looks pretty much like any other method that we would write even if we're not using asynchronous code so now let's take a look to see how our task code behaves when we're dealing with this manually before I make any changes to code let's go ahead and just run our application so we'll click on the fetch data with task and then wait our three seconds and then we'll see we do get an exception but notice that this exception is different from what we saw when we use the await operator the additional information says one or more errors occurred so it's not actually showing us the message from the exception that was thrown so what's going on here well exceptions normally stay on the thread that they were thrown on that means if we don't do anything special we're not going to get those exceptions across threads now the reason why we're seeing it here is because we're trying to access the result property of our task if an exception is thrown on that task then the task moves into what's known as the faulted State and if the task is in the faulted state the result property is not valid so if we try to access the result property on a faulted task we will get an exception message and that's exactly what we're seeing here so how should we take care of this there's actually a couple different approaches we can take this time what I'm going to show you is how we can be a little more specific with our continuations now when we looked at our continuous method last time we saw that there were 40 different overloads and in fact we're using this one right here that takes the continuation action and a task scheduler well there are a number of other options as well and I want to look for something that takes task continuation options and for that I know that we're looking for number 33 so I'm just going to jump ahead here now one of the problems with this is that even though it does have the task continuation options that I want as well as the task scheduler that I need it also wants a cancellation token well I'm not dealing with cancellation yet so what can I do here well we can actually pass in a placeholder cancellation token so I'm just gonna type in cancellation token and then notice how I have the red squigglies on this but I do have that little purple underline so do control dot and we'll see we have the option of adding using system dot threading to the top of our class so we'll go ahead and do that and then there is a static property that we can use which is cancellation token dot none so this will be a placeholder token that we don't have to interact with but we can still use it easily if we need it for a method parameter now what I really want to get to is the task continuation options because this is where things get interesting so tasks continuation options is actually an enum and we can see that there's a big list here to choose from so we can say deny child attach lazy cancellation etc but I really want to take a look at these ones that say not and only so we have not on canceled not on faulted and not on ran to completion so I can say I only want to run this continuation if the task is not in the faulted State and on the flip side we have the only x' so we have only on canceled only unfolded and only on ran to completion now in this case I want to use only on ran to completion so what that will mean is that our continuation will only run if our task completed and it's not faulted and it's also not canceled so let's go ahead and run our application right now to see what will happen so I click on the button and we'll see it's disabled and then we'll wait our three seconds and then we'll wait another three seconds so this continuation is not going to run because we did not complete successfully now the problem is the code that's renaming our button is also in that block so before we handle the exception I really want to pull that code out and for this what I'm going to do is I'm going to create another continuation now again there are a couple of different ways of dealing with this but I'm just going to say people tasks dot continue with and we'll use t with our goes to operator and we'll have the body of our land expression and inside here we're going to use our fetch with task button dot is enabled true now I do need the task scheduler because again I'm interacting with UI elements here so I am going to say task scheduler from current synchronization context but I don't need task continuation options in this case that's because I want this code to always run if things complete successfully it should run if I get an exception it should run and even if the process is canceled I want this to run so now let's run our application and see what happens so we click on the fetch with data button we see it's enabled and after 3 seconds it rien a Buhl's but notice nothing else happens like I said earlier exceptions will stay on the threads where they're generated so that not implemented exception is generated on the thread of our task so we don't see it on the UI thread but as you can imagine I can create another continuation that we can use for exception handling now to make things a little easier since there's a lot of parameters I'm just going to copy and paste our successful continuation and then we'll get rid of the body of the method for now and then I'm going to change my task continuation options from only on ran to completion - only on faulted so I only want this code to run if we do have a faulted task so let's go ahead and put in some of our exception handling code now again in this case I just want to show a message to the user to let them know that something went wrong with the process so just like when we were using the await operator I'll do a message box show and I need to get to the exception well it turns out task has a property called exception so I can say TDOT exception message and then again we'll say exception as the caption for this particular box now let's see what happens when we run our application so I click the button and then we'll wait our 3 seconds and then we'll see we do have a message box that pops up but again this doesn't have the error message that I expect it says one or more errors occurred it's not giving me the message from that not implemented exception now to understand why we need to take a look at the exceptions that we have in the task and why they are that way so back in our code if I hover over exception notice that this is an aggregate exception what's an aggregate exception well it's an exception that holds other exceptions now why do we need this well let's think about the power of tasks again we can have a parent that spawns off multiple child tasks we can have tasks that are combined into a single operation we can chain them together with continuations and we need to be able to manage those exceptions somehow so let's say that we have a task that kicks off five child tasks two of those tasks generate exceptions well that means our main task will go into a faulted State but what should the exception be well we don't want to just pick the first exception that we get so what we do is we take both of the exceptions that we got from the operation and put them into a single aggregate exception now this does have an inner exceptions property and with that we can look at all of the exceptions that have been collected but the problem is that inner exceptions collection can also contain aggregate exceptions so because we're dealing with tasks we don't know how many exceptions that will have and it could be aggregate exceptions all the way down so how do we navigate that tree well fortunately there's a way that we can deal with this so the first thing that I'm going to do is take a look at our TDOT exception and this has a method called flatten now what flatten will do is take all of those aggregate exceptions and smash them down to a single level so we no longer have this tree structure instead we just have a flattened collection of all of the exceptions that we're in there now we may or may not want to do this depending on what our application is doing but in our case we'll go ahead and flatten them so that we can see the exceptions that happened in a single collection so now I can look at the inner exceptions and this is a read-only collection of exception now one thing that I can do with a read-only collection is iterate over it with a for each loop and that's exactly what I'm going to do so we'll say for each bar e^x in inner exceptions and we'll do message box show but instead of doing it on the main exception we'll do one on each iterate that comes through now I wouldn't necessarily recommend doing this in a production application this way but what we can see is that it's really easy to get a collection of exceptions that we can iterate through in case we want to send them off to logging or some other system so now let's run our application and see how it behaves so when I click on the button we can see that it's disabled and then once our three seconds is done we do get our pop-up box but notice the message this time get method is not implemented so we're getting the actual message from the not implemented exception that was thrown in our asynchronous method and then if I click OK if there were other exceptions thrown then I would get those message boxes as well now in this case we only have a single exception so when I click OK our process is finished but before I do that I want to point out that the fetch data with tasks button has been re-enabled and that's different behavior than we had when we were using the await operator when we used a wait the button wasn't renamed until after we cleared our message box so let's go back to our code and see why this behavior is a little bit different now when we call our get method we get a task back that we assigned to our people tasks variable then we have three separate continuations on that same task now these continuations don't run in series they just run whenever they can according to what their current task scheduler is now if we look at our continue with method what we'll see is this also returns a task so if we wanted we could create an actual chain of tasks here where we wait for one continuation to complete and then call the next one wait for that one to complete and then call the next one now I won't do that in this case because we want our final continuation where we re enable the button to run whether it's successful or whether we get an exception now in the next episode we'll take a look at a different way of handling this where we just have a single continuation that would handle all of the states again the most powerful thing we get when we're dealing with tasks is the flexibility of how we put the different pieces together so let's review our functionality one more time when we're dealing with tasks directly we have a continuation for the success state and we note this by using the only ran to completion tasks continuation option and again since we're using the task scheduler from the current synchronization context we're specifying that we want this code to run on the UI thread now we have a second continuation and it has an only on faulted task continuation option specified that means this will only run if the task generates an exception and then on our last continuation we have no task continuation options specified that means we want this to always run and again we do need to be on the UI thread here because we're renaming one of the buttons on our screen now when we look at our await operator method we see this is much simpler so rather than having separate continuations we just have try catch finally blocks like we would have in our ordinary code and because of the simplicity and readability of this I would highly recommend using this wherever you can but it is good to know that the full power of tasks is available to us so that we can have our code do exactly what we need it to do so let's just run our application and see how our behavior is again so if I click fetch data with tasks our button is disabled and then when the exception comes back we have our exception pop up come up and our button is renamed when I click the fetch with a wait button again our button is disabled and then once it comes back we do show the exception message and then once we clear that our button is renamed now let's just make sure our success state works so we'll go back to our asynchronous method and I will comment out the exception and so now if we run our application wait click our fetch with tasks button and again our button disables and then once our data comes back a train ables and then fetch what the weight does the same thing so we can see that our code works both with our success state and if our task happens to throw an exception so that's it for our look at basic exception handling with tasks and a wait next time we'll dive a little bit deeper into some other options that we have so rather than having three separate continuations it's easy to create a single continuation and then use some properties on the task to figure out which code we want to run until then be sure to visit www.nasa.gov/twan
Info
Channel: Jeremy Clark
Views: 19,655
Rating: undefined out of 5
Keywords: C# (Programming Language), Await, Programming Language (Software Genre), Asynchronous Programming, Task, Exception Handling
Id: 2bFp7Ob-fC8
Channel Id: undefined
Length: 22min 5sec (1325 seconds)
Published: Wed Sep 02 2015
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.