【问题标题】:How to change the timeout on a .NET WebClient object如何更改 .NET WebClient 对象的超时
【发布时间】:2010-12-19 20:45:06
【问题描述】:

我正在尝试将客户端的数据下载到我的本地计算机(以编程方式),他们的网络服务器非常非常慢,这导致我的 WebClient 对象超时。

这是我的代码:

WebClient webClient = new WebClient();

webClient.Encoding = Encoding.UTF8;
webClient.DownloadFile(downloadUrl, downloadFile);

有没有办法在这个对象上设置无限超时?或者,如果没有,任何人都可以帮助我举一个替代方法的例子吗?

该网址在浏览器中运行良好 - 只需大约 3 分钟即可显示。

【问题讨论】:

    标签: c# .net file download webclient


    【解决方案1】:

    您可以延长超时时间:继承原始 WebClient 类并覆盖 webrequest getter 以设置您自己的超时时间,如下例所示。

    就我而言,MyWebClient 是一个私有类:

    private class MyWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri uri)
        {
            WebRequest w = base.GetWebRequest(uri);
            w.Timeout = 20 * 60 * 1000;
            return w;
        }
    }
    

    【讨论】:

    • 默认超时是多少??
    • 默认超时为 100 秒。虽然它似乎运行了 30 秒。
    • 使用 Timespan TimeSpan.FromSeconds(20).Milliseconds 设置超时更容易一些......不过是很好的解决方案!
    • @webwires 应该使用.TotalMilliseconds 而不是.Milliseconds
    • 类名应该是:PatientWebClient ;)
    【解决方案2】:

    第一个解决方案对我不起作用,但这里有一些对我有用的代码。

        private class WebClient : System.Net.WebClient
        {
            public int Timeout { get; set; }
    
            protected override WebRequest GetWebRequest(Uri uri)
            {
                WebRequest lWebRequest = base.GetWebRequest(uri);
                lWebRequest.Timeout = Timeout;
                ((HttpWebRequest)lWebRequest).ReadWriteTimeout = Timeout;
                return lWebRequest;
            }
        }
    
        private string GetRequest(string aURL)
        {
            using (var lWebClient = new WebClient())
            {
                lWebClient.Timeout = 600 * 60 * 1000;
                return lWebClient.DownloadString(aURL);
            }
        }
    

    【讨论】:

      【解决方案3】:

      您需要使用HttpWebRequest 而不是WebClient,因为您无法在不扩展WebClient 的情况下设置超时(即使它使用HttpWebRequest)。改用HttpWebRequest 将允许您设置超时。

      【讨论】:

      • 这不是真的...您可以在上面看到您仍然可以使用 WebClient,尽管自定义实现会覆盖 WebRequest 以设置超时。
      • "System.Net.HttpWebRequest.HttpWebRequest()' 已过时:'此 API 支持 .NET Framework 基础结构,不打算直接从您的代码中使用"
      • @usefulBee - 因为你不应该调用那个构造函数:"Do not use the HttpWebRequest constructor. Use the WebRequest.Create method to initialize new HttpWebRequest objects." from msdn.microsoft.com/en-us/library/…。另见stackoverflow.com/questions/400565/…
      • 澄清一下:虽然应该避免使用这个特定的构造函数(无论如何它不再是较新的 .NET 版本的一部分),但使用 HttpWebRequestTimeout 属性是完全可以的。以毫秒为单位。
      【解决方案4】:

      拔出网络电缆时无法使 w.Timeout 代码工作,它只是没有超时,转而使用 HttpWebRequest 并且现在可以完成工作。

      HttpWebRequest request = (HttpWebRequest)WebRequest.Create(downloadUrl);
      request.Timeout = 10000;
      request.ReadWriteTimeout = 10000;
      var wresp = (HttpWebResponse)request.GetResponse();
      
      using (Stream file = File.OpenWrite(downloadFile))
      {
          wresp.GetResponseStream().CopyTo(file);
      }
      

      【讨论】:

      • 这个答案很好用,但是对于任何感兴趣的人来说,如果你使用 var wresp = await request.GetResponseAsync(); 而不是 var wresp = (HttpWebResponse)request.GetResponse(); 你将再次获得大量超时
      • andrewjboyd:你知道为什么 GetResponseAsync() 不起作用吗?
      【解决方案5】:

      为了完整起见,这里是移植到 VB 的 kisp 的解决方案(不能在注释中添加代码)

      Namespace Utils
      
      ''' <summary>
      ''' Subclass of WebClient to provide access to the timeout property
      ''' </summary>
      Public Class WebClient
          Inherits System.Net.WebClient
      
          Private _TimeoutMS As Integer = 0
      
          Public Sub New()
              MyBase.New()
          End Sub
          Public Sub New(ByVal TimeoutMS As Integer)
              MyBase.New()
              _TimeoutMS = TimeoutMS
          End Sub
          ''' <summary>
          ''' Set the web call timeout in Milliseconds
          ''' </summary>
          ''' <value></value>
          Public WriteOnly Property setTimeout() As Integer
              Set(ByVal value As Integer)
                  _TimeoutMS = value
              End Set
          End Property
      
      
          Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
              Dim w As System.Net.WebRequest = MyBase.GetWebRequest(address)
              If _TimeoutMS <> 0 Then
                  w.Timeout = _TimeoutMS
              End If
              Return w
          End Function
      
      End Class
      
      End Namespace
      

      【讨论】:

        【解决方案6】:

        对于需要具有适用于异步/任务方法的超时的 WebClient 的任何人,建议的解决方案将不起作用。以下是有效的方法:

        public class WebClientWithTimeout : WebClient
        {
            //10 secs default
            public int Timeout { get; set; } = 10000;
        
            //for sync requests
            protected override WebRequest GetWebRequest(Uri uri)
            {
                var w = base.GetWebRequest(uri);
                w.Timeout = Timeout; //10 seconds timeout
                return w;
            }
        
            //the above will not work for async requests :(
            //let's create a workaround by hiding the method
            //and creating our own version of DownloadStringTaskAsync
            public new async Task<string> DownloadStringTaskAsync(Uri address)
            {
                var t = base.DownloadStringTaskAsync(address);
                if(await Task.WhenAny(t, Task.Delay(Timeout)) != t) //time out!
                {
                    CancelAsync();
                }
                return await t;
            }
        }
        

        我在博客上写了完整的解决方法here

        【讨论】:

        • 或者使用HttpClient
        【解决方案7】:

        正如 Sohnee 所说,使用 System.Net.HttpWebRequest 并设置 Timeout 属性而不是使用 System.Net.WebClient

        但是,您不能设置无限超时值(不支持,尝试这样做会引发 ArgumentOutOfRangeException)。

        我建议首先执行HEAD HTTP 请求并检查返回的Content-Length 标头值以确定您正在下载的文件中的字节数,然后为后续GET 请求设置相应的超时值或只需指定一个非常长的超时值,您永远不会期望超过该值。

        【讨论】:

          【解决方案8】:
          'CORRECTED VERSION OF LAST FUNCTION IN VISUAL BASIC BY GLENNG
          
          Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
                      Dim w As System.Net.WebRequest = MyBase.GetWebRequest(address)
                      If _TimeoutMS <> 0 Then
                          w.Timeout = _TimeoutMS
                      End If
                      Return w  '<<< NOTICE: MyBase.GetWebRequest(address) DOES NOT WORK >>>
                  End Function
          

          【讨论】:

            【解决方案9】:

            用法:

            using (var client = new TimeoutWebClient(TimeSpan.FromSeconds(10)))
            {
                return await client.DownloadStringTaskAsync(url).ConfigureAwait(false);
            }
            

            类:

            using System;
            using System.Net;
            
            namespace Utilities
            {
                public class TimeoutWebClient : WebClient
                {
                    public TimeSpan Timeout { get; set; }
            
                    public TimeoutWebClient(TimeSpan timeout)
                    {
                        Timeout = timeout;
                    }
            
                    protected override WebRequest GetWebRequest(Uri uri)
                    {
                        var request = base.GetWebRequest(uri);
                        if (request == null)
                        {
                            return null;
                        }
            
                        var timeoutInMilliseconds = (int) Timeout.TotalMilliseconds;
            
                        request.Timeout = timeoutInMilliseconds;
                        if (request is HttpWebRequest httpWebRequest)
                        {
                            httpWebRequest.ReadWriteTimeout = timeoutInMilliseconds;
                        }
            
                        return request;
                    }
                }
            }
            

            但我推荐一个更现代的解决方案:

            using System;
            using System.Net.Http;
            using System.Threading;
            using System.Threading.Tasks;
            
            public static async Task<string> ReadGetRequestDataAsync(Uri uri, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
            {
                using var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                if (timeout != null)
                {
                    source.CancelAfter(timeout.Value);
                }
            
                using var client = new HttpClient();
                using var response = await client.GetAsync(uri, source.Token).ConfigureAwait(false);
            
                return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            }
            

            它会在超时后抛出一个OperationCanceledException

            【讨论】:

            • 没有用,异步方法仍然可以无限期地工作
            • 可能问题不一样,需要使用ConfigureAwait(false)?
            【解决方案10】:

            根据 kisp 解决方案,这是我编辑的异步工作版本:

            WebConnection.cs

            internal class WebConnection : WebClient
            {
                internal int Timeout { get; set; }
            
                protected override WebRequest GetWebRequest(Uri Address)
                {
                    WebRequest WebReq = base.GetWebRequest(Address);
                    WebReq.Timeout = Timeout * 1000 // Seconds
                    return WebReq;
                }
            }
            

            异步任务

            private async Task GetDataAsyncWithTimeout()
            {
                await Task.Run(() =>
                {
                    using (WebConnection webClient = new WebConnection())
                    {
                        webClient.Timeout = 5; // Five seconds (the multiplication is in the override)
                        webClient.DownloadData("https://www.yourwebsite.com");
                    }
                });
            } // await GetDataAsyncWithTimeout()
            

            否则,如果您不想使用异步:

            private void GetDataSyncWithTimeout()
            {
                using (WebConnection webClient = new WebConnection())
                {
                    webClient.Timeout = 5; // Five seconds (the multiplication is in the override)
                    webClient.DownloadData("https://www.yourwebsite.com");
                }
            } // GetDataSyncWithTimeout()
            

            【讨论】:

            • 我会说返回任务而不是等待它更有意义。在第二种情况下,你也错过了 1000 毫秒的乘法
            • 乘以1000直接在override里面。
            【解决方案11】:

            我昨天不得不解决这个问题,我也最终编写了我的自定义扩展类。

            正如您通过查看下面的代码并将其与接受的答案进行比较所看到的那样,我尝试对建议进行更多调整,以便拥有更通用的类:这样您就可以设置精确的超时时间在使用使用内部 WebRequest 处理程序的方法之前实例化对象。

            using System;
            using System.Net;
            
            namespace Ryadel.Components.Web
            {
                /// <summary>
                /// An extension of the standard System.Net.WebClient
                /// featuring a customizable constructor and [Timeout] property.
                /// </summary>
                public class RyadelWebClient : WebClient
                {
                    /// <summary>
                    /// Default constructor (30000 ms timeout)
                    /// NOTE: timeout can be changed later on using the [Timeout] property.
                    /// </summary>
                    public RyadelWebClient() : this(30000) { }
            
                    /// <summary>
                    /// Constructor with customizable timeout
                    /// </summary>
                    /// <param name="timeout">
                    /// Web request timeout (in milliseconds)
                    /// </param>
                    public RyadelWebClient(int timeout)
                    {
                        Timeout = timeout;
                    }
            
                    #region Methods
                    protected override WebRequest GetWebRequest(Uri uri)
                    {
                        WebRequest w = base.GetWebRequest(uri);
                        w.Timeout = Timeout;
                        ((HttpWebRequest)w).ReadWriteTimeout = Timeout;
                        return w;
                    }
                    #endregion
            
                    /// <summary>
                    /// Web request timeout (in milliseconds)
                    /// </summary>
                    public int Timeout { get; set; }
                }
            }
            

            当我在那里时,我还趁机将默认的 Timeout 值降低到 30 秒,因为 100 对我来说似乎太多了。

            如果您需要有关此课程或如何使用它的更多信息,请查看我在博客上写的this post

            【讨论】:

              【解决方案12】:

              在某些情况下,需要将用户代理添加到标题中:

              WebClient myWebClient = new WebClient();
              myWebClient.DownloadFile(myStringWebResource, fileName);
              myWebClient.Headers["User-Agent"] = "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
              

              这就是我的情况的解决方案。

              学分:

              http://genjurosdojo.blogspot.com/2012/10/the-remote-server-returned-error-504.html

              【讨论】:

              • 与比赛完全无关。
              • @GrayProgrammerz 不,它不是如果您不指定用户代理,某些服务器将忽略您的超时
              猜你喜欢
              • 1970-01-01
              • 2012-09-01
              • 2012-04-02
              • 1970-01-01
              • 1970-01-01
              • 2013-01-11
              • 1970-01-01
              • 2012-03-09
              相关资源
              最近更新 更多