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

这是我的代码:

WebClient webClient = new WebClient();

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

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

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


当前回答

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

其他回答

用法:

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

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

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

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

根据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()

否则,如果你不想使用async:

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

我昨天不得不与这个问题作斗争,我也最终写了我的自定义扩展类。

通过查看下面的代码并将其与接受的答案进行比较,您可以看到,我试图稍微调整建议,以便拥有一个更通用的类:通过这种方式,您可以在实例化对象时或在使用使用内部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秒对我来说似乎太多了。

如果你需要关于这个类或如何使用它的额外信息,请查看我在博客上写的这篇文章。