【问题标题】:C# Async WebRequests: OnComplete EventC# 异步 WebRequests:OnComplete 事件
【发布时间】:2011-06-01 16:56:32
【问题描述】:

以下异步 C# 代码遍历 7 个 URL 的列表,并尝试从每个 URL 获取 HTML。现在我只是让它向控制台输出简单的调试响应,例如“站点 HTML”、“无响应”或“错误 URL”。它似乎工作正常,但是一旦完成所有 7 个查询,我就需要触发一个事件。我该怎么做?重要的是要考虑所有情况:1) 已收到站点 HTML,2) 站点超时,3) 站点是不正确的 URL,无法加载。我已经涵盖了所有这些情况,但不知道如何连接所有内容以触发全局“OnComplete”事件。

谢谢。

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

【问题讨论】:

    标签: c# asynchronous httpwebrequest


    【解决方案1】:

    您可以使用CountdownEvent 来了解所有网站何时已被扫描。它最初会设置为sites.Count,然后等待该事件。在每次完成时(通过错误、超时或成功),您都会发出事件信号。当事件计数达到零时,等待将返回,您可以进行“OnComplete”事件。

    【讨论】:

    • 添加“静态CountdownEvent evt;”向班级声明;在调用 ScanSites 之前初始化它 "evt = new CountdownEvent(_total);";调用 ScanSites 后,添加“evt.Wait()”。最后,在完成对特定站点的扫描的每个地方(ScanSites 的 foreach 循环中的捕获块,ResponseCallback 结束),您都可以调用“evt.Signal()”。
    • 是的,效果很好。我必须解决的最后一个问题是程序在某些站点上挂起,尽管超时。异步读取操作是否需要第二次超时 (ThreadPool.RegisterWaitForSingleObject)?我有一种感觉,这个抓取工具在阅读非常长的 HTML 页面时卡住了。
    【解决方案2】:

    恕我直言,最简单的方法是创建一个信号量,将每个 OnComplete 处理程序设置为 Release 它和 WaitOne 在主线程中对其进行 N 次(其中 N 是站点数)。

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

    另一种选择是将一些WaitHandleManualResetEvent 非常适合)传递给每个处理程序,并在主线程中为它们传递WaitHandle.WaitAll

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

    当然,您也可以使用 Interlocked 实现相同的效果,例如使用ExchangeCompareExchange 方法,但恕我直言,WaitHandles 在这里更直接(使用它们对性能的影响并不显着)。

    【讨论】:

      猜你喜欢
      • 2019-07-31
      • 2018-12-27
      • 2019-01-30
      • 2020-02-18
      • 2012-11-23
      • 1970-01-01
      • 2018-06-11
      • 2010-09-10
      • 2019-08-26
      相关资源
      最近更新 更多