[英]How to change the timeout on a .NET WebClient object
我正在嘗試將客戶端的數據下載到我的本地機器(以編程方式),並且他們的網絡服務器非常非常慢,這導致我的WebClient
對象超時。
這是我的代碼:
WebClient webClient = new WebClient();
webClient.Encoding = Encoding.UTF8;
webClient.DownloadFile(downloadUrl, downloadFile);
有沒有辦法在這個對象上設置無限超時? 或者,如果沒有,任何人都可以幫助我舉一個替代方法的例子嗎?
該 URL 在瀏覽器中運行良好 - 顯示只需大約 3 分鍾。
您可以擴展超時:繼承原始 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;
}
}
第一個解決方案對我不起作用,但這里有一些代碼對我有用。
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);
}
}
您需要使用HttpWebRequest
而不是WebClient
因為您無法在WebClient
上設置超時而不擴展它(即使它使用HttpWebRequest
)。 改用HttpWebRequest
將允許您設置超時。
拔出網絡電纜時無法使 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);
}
對於需要適用於異步/任務方法的超時 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;
}
}
我在這里寫了關於完整解決方法的博客
為了完整起見,這里是移植到 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
正如 Sohnee 所說,使用System.Net.HttpWebRequest
並設置Timeout
屬性而不是使用System.Net.WebClient
。
但是,您不能設置無限超時值(它不受支持並且嘗試這樣做會拋出ArgumentOutOfRangeException
)。
我建議首先執行HEAD HTTP請求並檢查返回的Content-Length
標頭值以確定您正在下載的文件中的字節數,然后為后續GET
請求相應地設置超時值或簡單地指定一個很長的超時您永遠不會期望超過的價值。
用法:
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
。
昨天我不得不與這個問題作斗爭,我也最終編寫了我的自定義擴展類。
正如您通過查看下面的代碼並將其與接受的答案進行比較所看到的,我嘗試對建議進行更多調整,以便擁有一個更通用的類:通過這種方式,您可以在實例化對象時設置精確的超時或在使用使用內部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
webClient.DownloadData("https://www.yourwebsite.com");
}
});
} // await GetDataAsyncWithTimeout()
否則,如果您不想使用異步:
private void GetDataSyncWithTimeout()
{
using (WebConnection webClient = new WebConnection())
{
webClient.Timeout = 5; // Five seconds
webClient.DownloadData("https://www.yourwebsite.com");
}
} // GetDataSyncWithTimeout()
在某些情況下,需要將用戶代理添加到標頭中:
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.