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();
}
}
|