我试图下载一个客户端的数据到我的本地机器(编程),他们的web服务器非常非常慢,这导致我的WebClient对象超时。

这是我的代码:

WebClient webClient = new WebClient();

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

有没有办法在这个对象上设置无限超时?如果不是,有没有人能帮我举个例子告诉我另一种方法?

该URL在浏览器中工作正常-只需要大约3分钟就能显示出来。


当前回答

你需要使用HttpWebRequest而不是WebClient,因为你不能在WebClient上设置超时而不扩展它(即使它使用HttpWebRequest)。使用HttpWebRequest将允许你设置超时。

其他回答

你需要使用HttpWebRequest而不是WebClient,因为你不能在WebClient上设置超时而不扩展它(即使它使用HttpWebRequest)。使用HttpWebRequest将允许你设置超时。

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

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

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

然而,你不能设置一个无限的超时值(它不受支持,试图这样做会抛出一个argumentoutofranceexception)。

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

对于任何需要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;
    }
}

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

用法:

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异常。