The .NET Framework's built-in WebClient
class does not have a built-in timeout feature.
So a lot of people suggest this code to solve the issue:
public class WebClientWithTimeout : WebClient { //10 secs default public int Timeout { get; set; } = 10000; protected override WebRequest GetWebRequest(Uri uri) { var w = base.GetWebRequest(uri); w.Timeout = Timeout; //10 seconds timeout return w; } }
The problem with this solution is that it does not work with async/await methods like DownloadStringTaskAsync
. Even if the underlying connection times out, the async Task
never returns (or returns after a default timeout, which is somewhat 100 or 500 seconds). I guess WebClient's async/awaitable methods are still in the works...
Anyway, I have found no other way but to "override" these methods and abort the task after a timeout:
public class WebClientWithTimeout : WebClient { //10 secs default public int Timeout { get; set; } = 10000; //let's hide the method with our "new" one 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(); //will throw "request aborted" exception, you will need to handle it } return await t; } }
But can we make it better? You wouldn't want to copy-paste the task awaiter into all the async methods you might need.
So let's create a RunWithTimeout
helper function, that simply moves the "timeouting" into a separate piece of code.
Here's the full listing
public class WebClientWithTimeout : WebClient { public int Timeout { get; set; } = 10000; //10 secs default //for sync requests protected override WebRequest GetWebRequest(Uri uri) { var w = base.GetWebRequest(uri); w.Timeout = Timeout; //10 seconds timeout return w; } //the above does not work for async requests, lets override the method public new async Task<string> DownloadStringTaskAsync(Uri address) { return await RunWithTimeout(base.DownloadStringTaskAsync(address)); } public new async Task<string> UploadStringTaskAsync(string address, string data) { return await RunWithTimeout(base.UploadStringTaskAsync(address, data)); } private async Task<T> RunWithTimeout<T>(Task<T> task) { if (task == await Task.WhenAny(task, Task.Delay(Timeout))) return await task; else { this.CancelAsync(); throw new TimeoutException(); } } }
Actually, the RunWithTimeout
is a very useful fella, and it might make sense to have it in some general static Extension class.