多个同时进行的WebClient http调用很难完成

时间:2015-08-25 16:04:54

标签: c# multithreading http iis

我在IIS中托管的网络服务中遇到了启动问题。该服务需要通过http online获取外部资源,以便在处理请求时使用。为了简化这些,这些资源在后台线程中收集并缓存。由于我不希望服务的初始请求失败,因此这些资源都会在启动期间立即收集。它们是使用System.Net.WebClient类下载的,所发生的是创建了一堆线程,这些线程都有一个WebClient对象,用于下载资源。

这表现得很奇怪。它几乎就像多个请求以某种方式阻塞对方,因为所发生的事情是所有请求都需要很长时间才能完成。当所有这些请求都在运行时,我可以调用一个本地托管的网站,该网站包含一个小型的#hello world" http页面需要几秒钟和几秒钟(使用我当前的设置,如果在后台线程启动之前调用它需要0.1秒,如果在启动后调用它需要30秒)。大约一分钟之后,事情就会恢复正常,而不是无法达到任何资源,它可以在一秒钟内突然下载大部分资源。

实验表明,这对并发线程或连接的限制不是问题(一分钟都没有任何成功的调用 - 如果存在某种连接限制,你会期待一些呼吁成功)。另一个有趣的实验是,当我尝试使用纯套接字连接到小型本地站点时 - 这完美无缺,因此它似乎是WebClient类的一个问题。

用于建立连接的类位于下面。调用TimeoutRequestHandler非常简单 - 使用uri和CustomWebClient创建它并调用进程请求。

    private class CustomWebClient : WebClient {

        public bool KeepAlive { get; set; }

        public X509Certificate ClientCertificate { get; set; }

        protected override WebRequest GetWebRequest(Uri address) {

            WebRequest answer = base.GetWebRequest(address);

            HttpWebRequest httpReq = answer as HttpWebRequest;
            if (httpReq != null) {
                httpReq.KeepAlive = KeepAlive;

                if (ClientCertificate != null) {
                    httpReq.ClientCertificates.Add(ClientCertificate);
                }
            }
            return answer;
        }
    }

    private class TimeoutRequestHandler {

        private readonly Uri address;
        private readonly WebClient client;
        private readonly byte[] requestData;
        private readonly TimeSpan timeout;

        private readonly object sync;
        private ManualResetEvent requestDoneSignal;
        private AsyncCompletedEventArgs completedInfo;

        public TimeoutRequestHandler(Uri address, WebClient client, byte[] requestData, TimeSpan timeout) {

            this.address = address;
            this.client = client;
            this.requestData = requestData;
            this.timeout = timeout;

            sync = new object();
            requestDoneSignal = new ManualResetEvent(false);
            client.UploadDataCompleted += OnRequestCompleted;
            client.DownloadDataCompleted += OnRequestCompleted;
        }

        public byte[] ProcessRequest() {

            bool shouldCancel = false;
            try {
                if (requestData != null) {
                    client.UploadDataAsync(address, requestData); // Uses POST for HTTP.
                } else {
                    client.DownloadDataAsync(address); // Uses GET for HTTP
                }
                if (!requestDoneSignal.WaitOne(timeout)) {
                    shouldCancel = true;
                    throw new WebException("The operation has timed out");
                }
                if (completedInfo.Cancelled) {
                    throw new WebException("The operation has been cancelled");
                }
                if (completedInfo.Error != null) {
                    throw completedInfo.Error;
                }
                return GetResponseData(completedInfo);
            } finally {
                AllDone(shouldCancel);
            }
        }

        private byte[] GetResponseData(AsyncCompletedEventArgs e) {

            return e is UploadDataCompletedEventArgs ?
                ((UploadDataCompletedEventArgs)e).Result :
                ((DownloadDataCompletedEventArgs)e).Result;
        }

        private void OnRequestCompleted(object sender, AsyncCompletedEventArgs e) {

            lock (sync) {
                if (requestDoneSignal != null) {
                    completedInfo = e;
                    requestDoneSignal.Set();
                }
            }
        }

        private void AllDone(bool shouldCancel) {

            lock (sync) {
                requestDoneSignal.Close();
                requestDoneSignal = null;
                client.UploadDataCompleted -= OnRequestCompleted;
                client.DownloadDataCompleted -= OnRequestCompleted;
                if (shouldCancel) {
                    client.CancelAsync();
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:0)

不确定它是否相关但我已经看到解决代理服务器的速度非常慢并且增加了大量开销的问题。我最近在项目中添加了以下行,以提高WebClient性能:

ServicePointManager.DefaultConnectionLimit = 256;
WebRequest.DefaultWebProxy = null;

ServicePointManager.DefaultConnectionLimit Property

  

DefaultConnectionLimit属性设置ServicePointManager对象在创建ConnectionLimit对象时分配给ServicePoint属性的默认最大并发连接数。

ServicePointManager.DefaultConnectionLimit的文档不明确,因为它声明:

  

默认值为Int32.MaxValue

但接着陈述:

  

在服务器环境中使用时(ASP.NET)DefaultConnectionLimit默认为更高的连接数,即10。

我刚刚在Visual Studio 2015中观看了.NET 4.5 WPF应用程序中的值,默认为2

System.Net.ServicePointManager.DefaultConnectionLimit defaults to 2

WebRequest.DefaultWebProxy Property

  

DefaultWebProxy属性获取或设置全局代理。 DefaultWebProxy属性确定所有WebRequest实例在请求支持代理时使用的默认代理,并且未使用Proxy属性显式设置代理。目前,FtpWebRequestHttpWebRequest支持代理。

     

DefaultWebProxy属性从app.config文件中读取代理设置。如果没有配置文件,则使用当前用户的Internet Explorer(IE)代理设置。

     

如果DefaultWebProxy属性设置为null,则WebRequestCreate方法创建的CreateDefault类的所有后续实例都没有代理。

以下配置参数也可用:

<system.net>
  <connectionManagement>
    <add address="*" maxconnection="256"/>
  </connectionManagement>
  <defaultProxy enabled="false" />
</system.net>

<connectionManagement> Element (Network Settings)

<defaultProxy> Element (Network Settings)

如果您有代理,显然在enabled="false"上设置defaultProxy将不起作用。在这种情况下,我会在配置中指定代理详细信息,因此不必检查IE。