back to Jitbit Blog home About this blog

WebClient Async (!) with Timeout

by Alex Yumashev · Updated Jan 23 2020

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.