Learn how to convert from synchronous networking code to asynchronous network code by using the begin-end pattern.
- [Instructor] We'll create a new project, File, New menu, new Project. Create another test project, call mine Networking, and in this UnitTest, we will test a download, and this will be similar to the Windows forms application we saw before. So in this method, we will conduct a download. We'll try and download from my webpage. So one of the things we want to look at in a bit of detail is how do we normally use networking code, and then what are the options for doing asynchronous network operations? And the first example will be the original, the begin/end invoke pattern.
Okay, so the webpage we want to download from we'll make this my webpage. But it's a very small one so it'll come down very quickly, so we'll make it go slow. Okay, you can also use a large download. Any website that's slow enough for you to notice that it takes a while. So in our test, we're going to do exactly what you would expect and create an httpWebRequest, issue the request, and then download the content. So you might remember HttpWebRequest is available from the System.Net library, and we'll ask it to Create.
This is a new method for us relatively speaking, httpWebRequest, based on the URL that we have in our variable above. And to get the HTTP response, we will ask the request object to actually issue the request, so we'll say GetResponse. This means get the response right now please. And we know it's HTTP, so we'll just make sure that cast happens. And so at this point we can also get the responseStream and things like that, ResponseInfo.GetResponseStream.
And of course, at this point, we're actually downloading the page content, right. But in the initial request this is where we actually made the round trip. So this is where the initial cost over the network was issued. And of course, subsequently, as we download the stream, as the content is downloaded that does take time. But this will cost us time and this will cost us time, and so we typically would not want to wait for those operations. Let me just complete the example. We will use a quick StreamReader, StreamReader from System.IO, using the StreamResponse stream that we got from our httpResponseInfo.
Using that StreamReader, we can read the responseStream into a variable of some kind. So the webpage itself is just sr.ReadToEnd(). We're not going to actually look at the content, but you can imagine when we have a certain URL available to us and we say GetResponse, the web server will respond with HTTP 200 if all is well, 404 if the page is not found, and so forth. And at that point, we know there's something there, we know what to do next. If there is content, we will ask for the download to happen.
If it's a large ISO file, then maybe this takes an hour. And then at some point we'll process that content as it is downloaded. So let's see that this works. Save All, Build, and there's our test. Right click, Run, we expect that to work. This is synchronous. This is how you would have done it in the past. Notice that this takes a moment. This will be five seconds for the download, and the first time it was a full 10 seconds. Okay, so let's convert this to a method that is going to be invoked asynchronously for getting the response, and so we will not be blocking the initial test method while we're fetching the response from the web server.
Let's just make some space on screen. I'll collapse the Solution and the Test Explorer, here we have it. Okay, so we know the initial round trip to the server is happening over here, so that's one of the places where we will use the begin method. So all of this will be commented out, we'll use it again later, and we'll change the GetResponse to BeginGetResponse. And now of course, the path end looks as before, we can provide a callback and some state.
We'll start with null, and so at this point we're not going to get an httpResponseInfo anymore. We are, of course, going to get iAsyncResult. So the async result is how we will correlate this begin with another end. And we would like to know when this completes. We would like to know when it finishes, so we would like to provide a callback method, and we will create a method for that, and notice the name of the method. When we call BeginGetResponse, all we're getting is the initial response from the server.
The content is yet to be downloaded. And so following this pattern, this method if it returns void, and takes this as an argument, we can use it as a callback. And there is the callback as a variable. So this variable is going to be our delegate, this delegate refers to HttpResponseAvailable, and that's the method that will be called once this completes. Okay, so what completes the GetResponse, the initial response from the server is available, and we're at the end of our test method.
So at this point, the test will exit and think things have gone well. So we will need to do a little bit more work to make it wait, but for now let's move the processing into the end here, and we'll connect the two. Okay, so IAsyncResult is how we correlate, how we coordinate between the caller and the callback. And for my convenience I'll pass the httpRequestInfo object in as the state. And so it will be available as an HttpWebRequest as this property AsyncState.
This property will contain whatever we put into this second parameter over here. And then with that httpRequestInfo, we can call EndRequest. And there's the EndGetResponse, and we provide the AsyncResult as correlation. And so at GetEndResponse, we know that's going to be HTTP. There we go, and so now we're back to the style we've had before. Uncomment httpResponseInfo like before.
We can ask it for the responseStream and the download continues as expected. Now we get two wins here. One is that the initial request to the server is going to be done on a background thread. So this code here is being done in the background asynchronously. And the bonus there for us, of course, is that once we are on the background thread dealing with the initial response, we're just going to continue on the same background thread and get the rest of the stream. Now watch what happens when I run this as a test. Save All, Build, go to Test Explorer, and Run.
Look how quickly this test ran, 14 milliseconds, even though we know this webpage will take five seconds. Just because we got all the way through the test code in the test method, launch the begin, and then exited the test method. So the test infrastructure, the unit testing framework here, doesn't know there's a background thread doing work that we need to wait for. And so again we would need to do some signaling. So this is artificial, of course, this original code would typically be doing something else, something better with its time.
But in our example, because we're testing, we're just going to wait here and say well this test is not completed until the download actually completes. Now you probably want to valid the content of the download. Save, Build, Test Explorer, Run, and that's good news, it takes a bit longer. There it is, six seconds. A minimum five for the delay in fetching the webpage plus some processing. And so there you have seen a very simple begin/end applied to an existing API.
The HTTP Request Library here is actually fetching an initial HTTP response for us in an asynchronous manner, so on a background thread, and then calls us back once it's available. And so once it's available, we complete our response, we may need to do some error handling here, and then we can actually process the page itself, the download activity follows. And so now you've seen the begin/end pattern applied though from inside a unit test, but by a library. So it wasn't us creating a delegate.
This httpRequestInfo object here was from the .NET library and you will see this all over. There are many, many methods available for databases, for networking, for storage, and all of them will have begin some operation followed by an end the same operation. You can follow this pattern throughout.
- 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