C# Async WebRequests: OnComplete Event
The following asynchronous C# code runs through a list of 7 URLs and tries to get HTML from each one. Right now I just have it outputting simple debug responses to the console like, "Site HTML", "No Response", or "Bad URL". It seems to work fine, but I need to fire off an event once all 7 queries have been made. How would I do this? It's important that all cases are taken into account: 1) Site HTML has been received, 2) Site timed out, 3) Site was an improper URL and couldn't be loaded. I'm covering all these cases already, but can't figure out how to connect everything to trigger a global "OnComplete" event.
Thank you.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;
using System.Timers;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace AsyncApp_05
{
class Program
{
static int _count = 0;
static int _total = 0;
static void Main(string[] args)
{
ArrayList alSites = new ArrayList();
alSites.Add("http://www.google.com");
alSites.Add("http://www.yahoo.com");
alSites.Add("http://www.ebay.com");
alSites.Add("http://www.aol.com");
alSites.Add("http://www.bing.com");
alSites.Add("adsfsdfsdfsdffd");
alSites.Add("http://wwww.fjasjfejlajfl");
alSites.Add("http://mundocinema.com/noticias/the-a-team-2/4237");
alSites.Add("http://www.spmb.or.id/?p=64");
alSites.Add("http://gprs-edge.ru/?p=3");
alSites.Add("http://blog.tmu.edu.tw/MT/mt-comments.pl?entry_id=3141");
_total = alSites.Count;
//Console.WriteLine(_total);
ScanSites(alSites);
Console.Read();
}
private static void ScanSites(ArrayList sites)
{
foreach (string uriString in sites)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriString);
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, 100, true);
}
catch (Exception ex)
{
Console.WriteLine("Bad URL");
Interlocked.Increment(ref _count);
}
}
}
static 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)
开发者_如何学C {
Console.WriteLine("Site HTML");
// 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();
}
}
}
static void ResponseCallback(IAsyncResult result)
{
Interlocked.Increment(ref _count);
Console.WriteLine("Count: " + _count);
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();
}
Console.WriteLine("No Response");
}
}
static void TimeOutCallback(object state, bool timedOut)
{
if (timedOut)
{
HttpWebRequest request = state as HttpWebRequest;
if (request != null)
{
request.Abort();
}
}
}
}
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;
}
}
}
You can use a CountdownEvent to find out when all the sites have been scanned. It'd be initially set to sites.Count, and then wait on that event. On each completion (either by error, timeout or success) you'd signal the event. When the event count reaches zero, the wait will return and you can have your "OnComplete" event.
The simplest way IMHO is to create a semaphore, make each OnComplete
handler to Release
it and to WaitOne
on it N times in a master thread (where N is number of sites).
private static void ScanSites(ArrayList sites)
{
var semaphore = new Semaphore(0,sites.Count);
foreach (string uriString in sites)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriString);
request.Method = "GET";
request.Proxy = null;
RequestState state = new RequestState();
state.Request = request;
state.Semaphore = semaphore;
IAsyncResult result = request.BeginGetResponse(new AsyncCallback(ResponseCallback), state);
// Timeout comes here
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle,
(o, timeout => { TimeOutCallback }, request, 100, true);
}
catch (Exception ex)
{
Console.WriteLine("Bad URL");
Interlocked.Increment(ref _count);
}
}
for(var i =0; i <sites.Count; i++) semaphore.WaitOne();
}
static void ReadCallback(IAsyncResult result)
{
try
{ ... }
finally{
var state = result.State as RequestState;
if (state != null) state.Semaphore.Release();
}
}
Another option is to pass some WaitHandle
(ManualResetEvent
fits well) to each of handler and WaitHandle.WaitAll
for them in master thread.
private static void ScanSites(ArrayList sites)
{
var handles = new List<WaitHandle>();
foreach (string uriString in sites)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriString);
request.Method = "GET";
request.Proxy = null;
RequestState state = new RequestState();
state.Request = request;
IAsyncResult result = request.BeginGetResponse(new AsyncCallback(ResponseCallback), state);
handles.Add(result.AsyncWaitHandle);
// Timeout comes here
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle,
new WaitOrTimerCallback(TimeOutCallback), request, 100, true);
}
catch (Exception ex)
{
Console.WriteLine("Bad URL");
Interlocked.Increment(ref _count);
}
}
WaitHandle.WaitAll(handles.ToArray());
}
Surely, you can achieve the same with Interlocked
as well, by using, e.g. Exchange
or CompareExchange
methods but IMHO, WaitHandle
s are more straightforward here (and performance hit for using them is not significant).
精彩评论