简体   繁体   English

如何更改 .NET WebClient 对象的超时时间

[英]How to change the timeout on a .NET WebClient object

I am trying to download a client's data to my local machine (programatically) and their webserver is very, very slow which is causing a timeout in my WebClient object.我正在尝试将客户端的数据下载到我的本地机器(以编程方式),并且他们的网络服务器非常非常慢,这导致我的WebClient对象超时。

Here is my code:这是我的代码:

WebClient webClient = new WebClient();

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

Is there a way to set an infinite timeout on this object?有没有办法在这个对象上设置无限超时? Or if not can anyone help me with an example on an alternate way to do this?或者,如果没有,任何人都可以帮助我举一个替代方法的例子吗?

The URL works fine in a browser - it just takes about 3 minutes to show.该 URL 在浏览器中运行良好 - 显示只需大约 3 分钟。

You can extend the timeout: inherit the original WebClient class and override the webrequest getter to set your own timeout, like in the following example.您可以扩展超时:继承原始 WebClient 类并覆盖 webrequest getter 以设置您自己的超时,如下例所示。

MyWebClient was a private class in my case:在我的例子中,MyWebClient 是一个私有类:

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

The first solution did not work for me but here is some code that did work for me.第一个解决方案对我不起作用,但这里有一些代码对我有用。

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

You need to use HttpWebRequest rather than WebClient as you can't set the timeout on WebClient without extending it (even though it uses the HttpWebRequest ).您需要使用HttpWebRequest而不是WebClient因为您无法在WebClient上设置超时而不扩展它(即使它使用HttpWebRequest )。 Using the HttpWebRequest instead will allow you to set the timeout.改用HttpWebRequest将允许您设置超时。

Couldn't get the w.Timeout code to work when pulled out the network cable, it just wasn't timing out, moved to using HttpWebRequest and does the job now.拔出网络电缆时无法使 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);
}

For anyone who needs a WebClient with a timeout that works for async/task methods , the suggested solutions won't work.对于需要适用于异步/任务方法的超时 WebClient 的任何人,建议的解决方案将不起作用。 Here's what does work:这是有效的方法:

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

I blogged about the full workaround here我在这里写了关于完整解决方法的博客

For completeness, here's kisp's solution ported to VB (can't add code to a comment)为了完整起见,这里是移植到 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
'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

As Sohnee says, using System.Net.HttpWebRequest and set the Timeout property instead of using System.Net.WebClient .正如 Sohnee 所说,使用System.Net.HttpWebRequest并设置Timeout属性而不是使用System.Net.WebClient

You can't however set an infinite timeout value (it's not supported and attempting to do so will throw an ArgumentOutOfRangeException ).但是,您不能设置无限超时值(它不受支持并且尝试这样做会抛出ArgumentOutOfRangeException )。

I'd recommend first performing a HEAD HTTP request and examining the Content-Length header value returned to determine the number of bytes in the file you're downloading and then setting the timeout value accordingly for subsequent GET request or simply specifying a very long timeout value that you would never expect to exceed.我建议首先执行HEAD HTTP请求并检查返回的Content-Length标头值以确定您正在下载的文件中的字节数,然后为后续GET请求相应地设置超时值或简单地指定一个很长的超时您永远不会期望超过的价值。

Usage:用法:

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

Class:班级:

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

But I recommend a more modern solution:但我推荐一个更现代的解决方案:

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

It will throw an OperationCanceledException after a timeout.它会在超时后抛出OperationCanceledException

I had to fight with this issue yesterday and I've also ended up to write my custom extension class.昨天我不得不与这个问题作斗争,我也最终编写了我的自定义扩展类。

As you can see by looking at the code below and comparing it with the accepted answer, I tried to tweak the suggestion a little bit more in order to have a more versatile class: this way you can set a precise timeout either upon instancing the object or right before using a method that uses the internal WebRequest handler.正如您通过查看下面的代码并将其与接受的答案进行比较所看到的,我尝试对建议进行更多调整,以便拥有一个更通用的类:通过这种方式,您可以在实例化对象时设置精确的超时或在使用使用内部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; }
    }
}

While I was there, I also took the chance to lower the default Timeout value to 30 seconds, as 100 seemed way too much for me.当我在那里时,我也借此机会将默认Timeout值降低到30秒,因为100对我来说似乎太多了。

In case you need additional info regarding this class or how to use it, check out this post I wrote on my blog.如果您需要更多关于这个类或如何使用它的其他信息,请查看这篇文章我在我的博客中写道。

According to kisp solution this is my edited version working async:根据 kisp 解决方案,这是我编辑的异步工作版本:

Class WebConnection.csWebConnection.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;
    }
}

The async Task异步任务

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

Else, if you don't want to use async:否则,如果您不想使用异步:

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

In some cases it is necessary to add user agent to headers:在某些情况下,需要将用户代理添加到标头中:

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)";

This was the solution to my case.这是我的情况的解决方案。

Credit:信用:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM