如何改变。net WebClient对象的超时时间

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

这是我的代码:

WebClient webClient = new WebClient();


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

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

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

217655 次浏览

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

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

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

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

你可以扩展超时:继承原始的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;
}
}

为了完整起见,下面是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
'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

不能让w.t otimeout代码在拔出网线时工作,它只是没有超时,移动到使用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);
}

第一个解决方案不适合我,但这里有一些代码确实适合我。

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

用法:

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

我在博客上写过完整的解决方法在这里

在某些情况下,有必要在头文件中添加用户代理:

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

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

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

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

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