20

I an creating a sample example to call link using WebClient using async and await method now I want to attach cancel async call functionality also. But I am not able to get CancellationTokenSource token and attach DownloadStringTaskAsync to this cancellation token. Following Is my code can anyone tell me how to accomplish this.

private async void DoWork()
        {
            this.Cursor = Cursors.WaitCursor;
            Write("DoWork started.");
            cts = new CancellationTokenSource();
            WebClient wc = new WebClient();
            string result = await wc.DownloadStringTaskAsync(new Uri("http://gyorgybalassy.wordpress.com"));

            if (result.Length < 100000)
            {
                Write("The result is too small, download started from second URL.");
                result = await wc.DownloadStringTaskAsync(new Uri("https://www.facebook.com/balassy"));
            }
            Write("Download completed. Downloaded bytes: " + result.Length.ToString());
            Write("DoWork ended.");
            this.Cursor = Cursors.Default;
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            Write("Cancellation started.");
            this.cts.Cancel();
            Write("Cancellation ended.");
        }

When my Cancel button calls cts.Cancel the DownloadStringTaskAsync call is not canceled. Why cancel button is not able to cancel the Async calls?

Balraj Singh
  • 3,381
  • 6
  • 47
  • 82
  • You're not using the `CancellationTokeSource` in any way, how is the `WebClient` supposed to know it should cancel when you don't tell it that? – svick Dec 10 '12 at 23:42
  • Thanks svick for your reply. But I tried to pass the token as a parameter of DownloadStringTaskAsync method but there is no overload for this method that supports it. Hence I was not getting how to use Cancellation token with DownloadStringTaskAsync method. Can you suggest me some good books to read all this new updates in C# with TAP capabilities. – Balraj Singh Dec 11 '12 at 05:24

3 Answers3

31

The async capabilities of WebClient predate .Net 4.5, so it supports the Task-based Asynchronous Pattern only partially. That includes having its own cancellation mechanism: the CancelAsync() method, which works even with the new -TaskAsync methods. To call this method when a CancellationToken is canceled, you can use its Register() method:

cts.Token.Register(wc.CancelAsync);

As an alternative, you could use the new HttpClient, as Stephen suggested, which fully supports TAP, including CancellationTokens.

svick
  • 236,525
  • 50
  • 385
  • 514
  • 4
    It probably wouldn't hurt to dispose the `CancellationTokenRegistration` returned by the `cts.Token.Register` after the download completes. – Brian Nov 20 '19 at 20:32
5

Extension methods based on svick's answer:

public static async Task<string> DownloadStringTaskAsync(this WebClient webClient, string url, CancellationToken cancellationToken) {
    using (cancellationToken.Register(webClient.CancelAsync)) {
        return await webClient.DownloadStringTaskAsync(url);
    }
}

public static async Task<string> DownloadStringTaskAsync(this WebClient webClient, Uri uri, CancellationToken cancellationToken) {
    using (cancellationToken.Register(webClient.CancelAsync)) {
        return await webClient.DownloadStringTaskAsync(uri);
    }
}
svick
  • 236,525
  • 50
  • 385
  • 514
Error404
  • 719
  • 9
  • 30
  • That is actually [Microsoft recommended way](https://learn.microsoft.com/en-us/dotnet/standard/threading/how-to-register-callbacks-for-cancellation-requests) see the example code, it even uses `WebClient` as an example! – Alex from Jitbit Oct 08 '21 at 10:01
4

WebClient doesn't support cancellation. I recommend you use a newer type such as HttpClient:

...
cts = new CancellationTokenSource();
string result;
using (var client = new HttpClient())
using (var response = await client.GetAsync("http://gyorgybalassy.wordpress.com", cts.Token))
{
  result = await response.Content.ReadAsStringAsync();
}

if (result.Length < 100000)
...

The GetAsync method by default will not complete until it reads the entire response, so the await response.Content.ReadAsStringAsync line will actually complete synchronously.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • small nitpick: `HttpClient` is [designed for reuse](https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-5.0#pool-http-connections-with-httpclientfactory) don't put it inside `using` – Alex from Jitbit Oct 08 '21 at 09:09