【问题标题】:Multiple simultaneous WebClient http calls are slow to finish多个同时 WebClient http 调用完成缓慢
【发布时间】:2015-08-25 16:04:54
【问题描述】:

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

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

实验表明,这不是并发线程或连接限制的问题(一分钟内根本没有任何成功的调用 - 如果存在某种连接限制,您会期望一些调用成功) .另一个有趣的实验是,当我尝试使用纯套接字连接到小型本地站点时 - 这工作完美无缺,所以这似乎是 WebClient 类特有的问题。

用于建立连接的类如下所示。对 TimeoutRequestHandler 的调用非常简单 - 您可以使用 uri 和 CustomWebClient 创建它,然后调用 process request。

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

【问题讨论】:

    标签: c# multithreading http iis


    【解决方案1】:

    不确定它是否相关,但我发现解析代理服务器可能非常缓慢并且会增加大量开销。我最近在一个项目中添加了以下几行来提高WebClient 的性能:

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

    ServicePointManager.DefaultConnectionLimit Property:

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

    ServicePointManager.DefaultConnectionLimit 的文档并不清楚,因为它指出:

    默认值为Int32.MaxValue

    然后继续说明:

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

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

    WebRequest.DefaultWebProxy Property:

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

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

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

    以下配置参数也可用:

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

    <connectionManagement> Element (Network Settings)

    <defaultProxy> Element (Network Settings)

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

    【讨论】:

    • 感谢您的回复。但是,这对我没有帮助,因为我已经明确地将代理设置为 null 并且 DefaultConnectionLimit 现在默认为 Int32.MaxValue (根据我在网上阅读的内容,我认为它曾经默认为 2)。我已经尝试过更改它而没有注意到任何效果。
    • 是的,ServicePointManager.DefaultConnectionLimit 的文档不清楚,因为它表明默认值为 Int32.MaxValue,但随后继续声明:“在服务器环境 (ASP.NET) 中使用时,DefaultConnectionLimit 默认为更高的连接数,即 10。”。我刚刚查看了我的应用程序中的值(VS 2015 中的 WPF .NET 4.5),默认情况下它是 2。也就是说,如果您在没有任何效果的情况下增加了它,这些都不重要。我似乎记得另一篇文章提到 DNS 解析很慢,另一篇文章谈到延迟旋转 ServicePoints。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-17
    • 2018-10-16
    • 1970-01-01
    • 2013-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多