Making an asynchronous WebRequest

From: http://holyhoehle.wordpress.com/2010/01/15/making-an-asynchronous-webrequest/

Making an asynchronous WebRequest

When downloading large amounts of data a normal synchronous web request, as described in the previous article, may block your current thread. The solution for this is to make an asynchronous web request. To start an asynchronous webrequest WebRequest.BeginGetResponse() has to be called. But before we can do that, we have to write some decent lines of code.

At first we have to create an object that stores the state of the request. To do this we create a new class RequestState:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RequestState
{
     public int BufferSize { get ; private set ; }
     public StringBuilder ResponseContent { get ; set ; }
     public byte [] BufferRead { get ; set ; }
     public HttpWebRequest Request { get ; set ; }
     public HttpWebResponse Response { get ; set ; }
     public Stream ResponseStream { get ; set ; }
 
     public RequestState()
     {
         BufferSize = 1024;
         BufferRead = new byte [BufferSize];
         ResponseContent = new StringBuilder();
         Request = null ;
         ResponseStream = null ;
     }
}

After that we need to create a Callback method to process the response. This has to be in the form of the AsyncCallback delegate:

1
public delegate void AsyncCallback(IAsyncResult ar);

So here’s our method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void ResponseCallback(IAsyncResult result)
{
     try
     {
         // Get and fill the RequestState
         RequestState state = (RequestState)result.AsyncState;
         HttpWebRequest request = state.Request;
         // End the Asynchronous response and get the actual resonse object
         state.Response = (HttpWebResponse)request.EndGetResponse(result);
         Stream responseStream = state.Response.GetResponseStream();
         state.ResponseStream = responseStream;
 
         // Begin async reading of the contents
         IAsyncResult readResult = responseStream.BeginRead(state.BufferRead,
                 0, state.BufferSize, new AsyncCallback(ReadCallback), state);
     }
     catch (Exception ex)
     {
         // Error handling
         RequestState state = (RequestState)result.AsyncState;
         if (state.Response != null )
             state.Response.Close();
     }
}

As you can see, we need another Callback method for our asynchronous read operation. We also just could call the responseStream.ReadToEnd() method to get the response data but that would again block the calling thread. So the response should also be read asynchonously.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private void ReadCallback(IAsyncResult result)
{
     try
     {
         // Get RequestState
         RequestState state = (RequestState)result.AsyncState;
         // determine how many bytes have been read
         int bytesRead = state.ResponseStream.EndRead(result);
 
         if (bytesRead > 0) // stream has not reached the end yet
         {
             // append the read data to the ResponseContent and...
             state.ResponseContent.Append(Encoding.ASCII.GetString(state.BufferRead, 0, bytesRead));
             // ...read the next piece of data from the stream
             state.ResponseStream.BeginRead(state.BufferRead, 0, state.BufferSize,
                 new AsyncCallback(ReadCallback), state);
         }
         else // end of the stream reached
         {
             if (state.ResponseContent.Length > 0)
             {
                 // do something with the response content, e.g. fill a property or fire an event
                 AsyncResponseContent = state.ResponseContent.ToString();
                 // close the stream and the response
                 state.ResponseStream.Close();
                 state.Response.Close();
                 OnAsyncResponseArrived(AsyncResponseContent);
             }
         }
     }
     catch (Exception ex)
     {
         // Error handling
         RequestState state = (RequestState)result.AsyncState;
         if (state.Response != null )
             state.Response.Close();
     }
}

Now that we have written all this code, we are finally ready to call WebRequest.BeginGetResponse() and pass in an instance of the RequestState class and the ResponseCallback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void MakeWebRequestAsync( string url)
{
     try
     {
         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
         request.Method = "GET" ;
         request.Proxy = null ;
 
         RequestState state = new RequestState();
         state.Request = request;
 
         IAsyncResult result = request.BeginGetResponse( new AsyncCallback(ResponseCallback), state);
     }
     catch (Exception ex)
     {
         // Error handling
     }
}

Implementing a timeout functionality

When making an asynchronous webrequest the WebRequest.Timeout property will be ignored. So we have to implement our own timeout machanism. The following lines will demonstrate how to accomplish that task.

First we need to register a timeout delegate after the request.BeginGetResponse() call:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void MakeWebRequestAsync( string url, int timeOut)
{
     try
     {
         HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
         request.Method = "GET" ;
         request.Proxy = null ;
 
         RequestState state = new RequestState();
         state.Request = request;
 
         IAsyncResult result = request.BeginGetResponse( new AsyncCallback(ResponseCallback), state);
 
         // Timeout comes here
         ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle,
             new WaitOrTimerCallback(TimeOutCallback), request, timeOut, true );
     }
     catch (Exception ex)
     {
         // Error handling
     }
}

In the TimeOutCallback we’re just aborting the webrequest. It looks something like this:

1
2
3
4
5
6
7
8
9
private void TimeOutCallback( object state, bool timedOut)
{
     if (timedOut)
     {
         HttpWebRequest request = state as HttpWebRequest;
         if (request != null )
             request.Abort();
     }
}

你可能感兴趣的:(Making an asynchronous WebRequest)