Making Asynchronous Requests
The System.Net classes use the .NET Framework's standard asynchronous programming model for asynchronous access to Internet resources. The BeginGetResponse and EndGetResponse methods of the WebRequest class start and complete asynchronous requests for an Internet resource.
Using synchronous calls in asynchronous callback methods can result in severe performance penalties. Internet requests made with WebRequest and its descendants must use Stream.BeginRead to read the stream returned by the WebResponse.GetResponseStream method.
The following sample code demonstrates how to use asynchronous calls with the WebRequest class. The sample is a console program that takes a URI from the command line, requests the resource at the URI, and then prints data to the console as it is received from the Internet.
The program defines two classes for its own use, the RequestState class, which passes data across asynchronous calls, and the ClientGetAsync class, which implements the asynchronous request to an Internet resource.
The RequestState class preserves the state of the request across calls to the asynchronous methods that service the request. It contains WebRequest and Stream instances that contain the current request to the resource and the stream received in response, a buffer that contains the data currently received from the Internet resource, and a StringBuilder that contains the complete response. A RequestStateis passed as the state parameter when the AsyncCallback method is registered with WebRequest.BeginGetResponse.
The ClientGetAsync class implements an asynchronous request to an Internet resource and writes the resulting response to the console. It contains the methods and properties described in the following list.
The allDone property contains an instance of the ManualResetEvent class that signals the completion of the request.
The Main() method reads the command line and begins the request for the specified Internet resource. It creates the WebRequest wreq and the RequestState rs, calls BeginGetResponse to begin processing the request, and then calls the allDone.WaitOne()method so that the application will not exit until the callback is complete. After the response is read from the Internet resource, Main() writes it to the console and the application ends.
The showusage() method writes an example command line on the console. It is called by Main() when no URI is provided on the command line.
The RespCallBack() method implements the asynchronous callback method for the Internet request. It creates the WebResponse instance containing the response from the Internet resource, gets the response stream, and then starts reading the data from the stream asynchronously.
The ReadCallBack() method implements the asynchronous callback method for reading the response stream. It transfers data received from the Internet resource into the ResponseData property of the RequestState instance, then starts another asynchronous read of the response stream until no more data is returned. Once all the data has been read, ReadCallBack() closes the response stream and calls the allDone.Set() method to indicate that the entire response is present in ResponseData.
It is critical that all network streams are closed. If you do not close each request and response stream, your application will run out of connections to the server and be unable to process additional requests.
Imports System Imports System.Net Imports System.Threading Imports System.Text Imports System.IO ' The RequestState class passes data across async calls. Public Class RequestState Public RequestData As New StringBuilder("") Public BufferRead(1024) As Byte Public Request As HttpWebRequest Public ResponseStream As Stream ' Create Decoder for appropriate encoding type. Public StreamDecode As Decoder = Encoding.UTF8.GetDecoder() Public Sub New Request = Nothing ResponseStream = Nothing End Sub End Class ' ClientGetAsync issues the async request. Class ClientGetAsync Shared allDone As New ManualResetEvent(False) Const BUFFER_SIZE As Integer = 1024 Shared Sub Main() Dim Args As String() = Environment.GetCommandLineArgs() If Args.Length < 2 Then ShowUsage() Return End If ' Get the URI from the command line. Dim HttpSite As Uri = New Uri(Args(1)) ' Create the request object. Dim wreq As HttpWebRequest = _ CType(WebRequest.Create(HttpSite), HttpWebRequest) ' Create the state object. Dim rs As RequestState = New RequestState() ' Put the request into the state so it can be passed around. rs.Request = wreq ' Issue the async request. Dim r As IAsyncResult = _ CType(wreq.BeginGetResponse( _ New AsyncCallback(AddressOf RespCallback), rs), IAsyncResult) ' Wait until the ManualResetEvent is set so that the application ' does not exit until after the callback is called. allDone.WaitOne() End Sub Shared Sub ShowUsage() Console.WriteLine("Attempts to GET a URI") Console.WriteLine() Console.WriteLine("Usage:") Console.WriteLine("ClientGetAsync URI") Console.WriteLine("Examples:") Console.WriteLine("ClientGetAsync http://www.contoso.com/") End Sub Shared Sub RespCallback(ar As IAsyncResult) ' Get the RequestState object from the async result. Dim rs As RequestState = CType(ar.AsyncState, RequestState) ' Get the HttpWebRequest from RequestState. Dim req As HttpWebRequest= rs.Request ' Call EndGetResponse, which returns the HttpWebResponse object ' that came from the request issued above. Dim resp As HttpWebResponse = _ CType(req.EndGetResponse(ar), HttpWebResponse) ' Start reading data from the respons stream. Dim ResponseStream As Stream = resp.GetResponseStream() ' Store the reponse stream in RequestState to read ' the stream asynchronously. rs.ResponseStream = ResponseStream ' Pass rs.BufferRead to BeginRead. Read data into rs.BufferRead. Dim iarRead As IAsyncResult = _ ResponseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZE, _ New AsyncCallback(AddressOf ReadCallBack), rs) End Sub Shared Sub ReadCallBack(asyncResult As IAsyncResult) ' Get the RequestState object from the AsyncResult. Dim rs As RequestState = CType(asyncResult.AsyncState, RequestState) ' Retrieve the ResponseStream that was set in RespCallback. Dim responseStream As Stream = rs.ResponseStream ' Read rs.BufferRead to verify that it contains data. Dim read As Integer = responseStream.EndRead( asyncResult ) If read > 0 Then ' Prepare a Char array buffer for converting to Unicode. Dim charBuffer(1024) As Char ' Convert byte stream to Char array and then String. ' len contains the number of characters converted to Unicode. Dim len As Integer = _ rs.StreamDecode.GetChars(rs.BufferRead, 0, read, charBuffer, 0) Dim str As String = new String(charBuffer, 0, len) ' Append the recently read data to the RequestData stringbuilder ' object contained in RequestState. rs.RequestData.Append( _ Encoding.ASCII.GetString(rs.BufferRead, 0, read)) ' Continue reading data until responseStream.EndRead ' returns –1. Dim ar As IAsyncResult = _ responseStream.BeginRead(rs.BufferRead, 0, BUFFER_SIZE, _ New AsyncCallback(AddressOf ReadCallBack), rs) Else If rs.RequestData.Length > 1 Then ' Display data to the console. Dim strContent As String strContent = rs.RequestData.ToString() Console.WriteLine(strContent) End If ' Close down the response stream. responseStream.Close() ' Set the ManualResetEvent so the main thread can exit. allDone.Set() End If Return End Sub End Class