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

259
我想要以程序化的方式将客户端的数据下载到我的本地机器上,但是他们的Web服务器非常慢,这导致我的WebClient对象超时。
这是我的代码:
WebClient webClient = new WebClient();

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

有没有办法在该对象上设置无限超时时间?如果没有,是否有人能帮我提供一个替代方法的示例?

在浏览器中URL正常工作,只是需要大约3分钟才会显示出来。


谢谢您的提问。如果我知道您无法在WebClient上设置超时时间,我就不会使用它了。当新工具比当前工具差劲时,这真是令人恼火。 - Belmiris
13个回答

429
你可以延长超时时间:继承原始的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;
    }
}

6
默认超时时间是多少? - knocte
26
默认超时时间为100秒。尽管它看起来运行了30秒。 - Carter Medlin
3
使用TimeSpan.FromSeconds(20)可以更轻松地设置超时时间,.Milliseconds是表示毫秒的单位...这个方法很好! - webwires
23
应该使用.TotalMilliseconds而不是.Milliseconds - Alexander Galkin
96
类的名称应该是:PatientWebClient ;) - Jan Willem B
显示剩余6条评论

35

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

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

24

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


这并不是真的...你可以看到上面提到,你仍然可以使用WebClient,尽管需要自定义实现来覆盖WebRequest以设置超时。 - DomenicDatti
8
"System.Net.HttpWebRequest.HttpWebRequest()"已经过时:'此API支持.NET Framework基础架构,不建议直接从您的代码中使用。' - usefulBee
4
@usefulBee - 因为你不应该调用那个构造函数:"请勿使用 HttpWebRequest 构造函数。使用 WebRequest.Create 方法来初始化新的 HttpWebRequest 对象。",参考链接为 https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest(v=vs.110).aspx。同时请查看 https://dev59.com/V0bRa4cB1Zd3GeqPxBvM - ToolmakerSteve
仅澄清一下:虽然应该避免使用这个特定的构造函数(无论如何,它已经不再是较新的.NET版本的一部分),但使用HttpWebRequestTimeout属性完全没有问题。它以毫秒为单位。 - Marcel

13

对于任何需要具有超时功能的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;
    }
}

我在这里写了关于完整解决方案的博客(链接)


或者使用HttpClient。 - Tonko Boekhoud

11

在拔掉网络电缆时,我无法让 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);
}

1
这个答案非常好,但是对于任何感兴趣的人,如果你使用var wresp = await request.GetResponseAsync();而不是var wresp = (HttpWebResponse)request.GetResponse();,你将再次获得巨大的超时。 - Andrew Boyd
andrewjboyd:你知道为什么 GetResponseAsync() 不起作用吗? - osexpert

11

为了完整起见,这里是将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

作者要求提供 C# 语言的解决方案,而不是 Visual Basic。 - VladStepu2001
1
为了完整性,这是一个VB解决方案,供使用VB的读者阅读本站并可能需要C#代码移植的人使用。就像我自己一样。 - GlennG

7
如Sohnee所说,使用System.Net.HttpWebRequest并设置Timeout属性,而不是使用System.Net.WebClient
但是您不能将超时值设置为无限大(不支持这样做,否则会抛出ArgumentOutOfRangeException异常)。
我建议首先执行HEAD HTTP请求,并检查返回的Content-Length头部值以确定要下载的文件中的字节数,然后相应地设置后续的GET请求的超时值,或者仅指定一个非常长的超时值,您永远不希望超过该值。

7

使用方法:

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


没有起作用,异步方法仍然无限期地工作。 - Alex from Jitbit
也许问题不同,你需要使用ConfigureAwait(false)吗? - Konstantin S.

7
'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

1
根据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毫秒的乘法。 - Yehor Androsov
乘以1000的操作直接在覆盖函数中。 - Marco Concas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接