Learn how to use tasks with the asynchronous API of the Networking classes.
- [Instructor] Now that we have seen the basics of the task class, let's use it as part of our network and database asynchronous code. You may remember we had networking code before, where we tried to do a download asynchronously. We started out with just going to a URL while the calling thread is blocked, so we said create HTTP web request, based on a URL. We had the AsyncCallback. This AsyncCallback was how we're going to notify ourselves that the download is available, and so when we say BeginGetResponse at this point, it's an asynchronous call.
At this point we say please don't block us, return as soon as possible, have the work done on a background thread. Now, because we're in a testing framework, of course, we have to wait, and so begin began the job on the background thread, and once that job is complete, the callback to HttpResponseAvailable is invoked and so here, we run our code on a background thread once the HTTP response is available. Now the implementation details is left to the .net framework in terms of how much work is done on the calling thread and how it actually does the waiting for the server to respond, but once we get to the callback, this is running on a background thread.
We are consciously aware of this, in that we set up the callback, we set up a delegate to call us back, and then we have to deal with the async result. This is not strictly required. With the task class we can actually do this very differently. So I'm going to copy and paste all of this. Copy all the way to the bottom, paste, new method name, so we can do this asynchronously with a task, and this method, just make it number two for now, and modify pretty much everything about it.
Okay, so we still want to do a network request. We still want to do it asynchronously, but we don't want to explicitly have to deal with the callback here. Okay, so first things first, we are not going to use the callback method in that way, and we're not going to call GetResponse as a BeginGetResponse. Instead, we're going to call HttpRequestInfo.GetResponseAsync. Now you do need to have a reasonably recent version of .net to get that method, and you'll find that if you're in .net one or such, it's not going to be there.
You need the task libraries for this to actually work. So, what do we do? We receive back a task, a task generic type with a web response type parameter. So let's just write that out. So what we are being returned here, is something from the task library, so control dot, using System.Threading.Tasks and so now this little task here is the thing that tells us, in abstract sense, a promise is being made this will be done in the background, and of course we can ask it have you completed yet, and so forth.
So for the sake of the testing framework, we will ask this question, and we'll just ask please wait until you're done. This won't be sufficient, but for now it's good enough. So the GetResponseAsync is being invoked and a task is being returned. So once that task completes, then what? Well, we want the response, we want the WebResponse object. How do we get our hands on that? Create a new task. So taskWebResponse.ContinueWith. Now this is similar, but not quite the same as our callback method.
This will yield a new task, and when we say ContinueWith, the result will be just a task. And what do we want to continue with? An action, so delegate, that accepts a task with a web response type parameter. So, what we're looking for is a method that takes a task with a web response. So now you can see we're tying it together in a similar pattern to having a callback, but now we're dealing with tasks, and so we can wait on all tasks, we can look at exceptions on a per task basis, we can send cancellations and all sorts of mechanisms like that are available when we're dealing with a task model rather than the callback methods being explicitly chained together.
And so now, with the ContinueWith method here, let's just rename it, give it a better name, and let's rename the task as well, so that it might be a little easier to read. Now we want to wait for the WebResponse task here, but we also want to wait for the continuation to complete. This is for the sake of the unit test, if this was production code, you may not need to wait at all at this point or on this thread. Now when we say ContinueWith, bare in mind that this is a WebRequest, goes over the internet, probably it's going to fail from time to time.
So when we say ContinueWith, bare in mind you have more options. Continuation is a cancellation token, next option ContinuationOptions, and just look at that. We would, for example, in this case, only want to proceed with the continuation if the initial task ran to completion. So now you can model for, design for dealing with a certain method that deals with an error, different method that deals with success and process results.
And so, suddenly, your model of how you design your application becomes much more event-driven, if that's the right word, certainly task-based, so we schedule tasks to be done, and some tasks will be done under successful circumstances, some tasks to handle errors, but they're nice, concise chunks of code that happen under certain circumstances. So, let's look at our method, this continuation method here, what is available to us. TaskResponse actually has a result.
A result that is of type WebResponse, which is what we were looking for in the first place. So the web response, httpResponseInfo down here, is available to us as the result object. Copy, paste, now remove this now, no longer need this. Now we know we want the HTTP members, and that cast will work, and so now, the httpResponseInfo that we had before is available just like in the previous code example, and so there's very little else for us to do at this stage other than additional production error handling and such.
So, we have actually simplified our code quite a bit, and you can see here at the top, we started with the same httpRequestInfo object. We set up a task for doing the GetResponseAsync, then we hooked up a second task, which is similar to what we would have done for the callback, but instead of our callback method running no matter what, we will now only run our method on the initial task completing successfully. And we can have a different task for error handling, if at all. Now we still need to wait for these to complete for the sake of the unit test, but let's hit the break point here so we can see this run and I can just validate, didn't make any mistakes.
Control shift B, go look at our unit tests, the AsyncTask, I'll debug. Let's see what happens. Think we've got our breakpoint, and remember this was a five second delay on this URL, and the web page, there it is. Notice that we're on a background thread, where's our yellow arrow, way down here, workers thread. And so the task is being done in the background, test completed successfully, and the unit test framework was able to wait appropriately until both tasks completed.
Now just from a syntax point of view, I prefer this. It's a nice, tidy way to do that. And so now you've seen a fairly concise way to write under successful circumstances a task for running off to the download completes or off to the initial web request completes to conduct the download, and another method can be written for handling errors, and those can be chained together so we have the initial task and one or more continuations subject to the continuation options.
We can do similar things for error handling, and so now we are just using the thread pool for these background threads, but of course we also have the task factory and control the scheduler if we need those advanced options, and so now we have a concise way of writing tasks, tasks with continuations, and this will simplify what used to be fairly complex callback methods, especially when we had to chain one to the other. Also simplifies signaling, so being able to ask for waiting on one or more tasks, and of course tasks still have their status and other things like that and we can query should it be necessary.
And so now you've seen the option of the begin/end pattern, you've seen the option of creating threads yourself, and you've seen the option of tasks and essentially using a thread pool, but with the infrastructure of tasks making it easier to manage.
- What is asynchronous?
- Blocking vs. nonblocking I/O
- Async database queries with begin/end
- Windows Forms BackgroundWorker
- Async networking with Tasks
- Async database queries with Tasks
- C# keywords async and await