Is the code sample correct?
It disposes of the CancellationTokenRegistration - which will unregister the callback - as soon as it finishes calling DownloadStringAsync(), which seems somewhat pointless!
using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync()))
{
Console.WriteLine("Starting request\n");
wc.DownloadStringAsync(new Uri("http://www.contoso.com"));
}
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.
It's correct. This isn't synchronous code you're looking at. The use of Task.Run means it'll execute on another thread, so while the download operation is running, the console will display the prompt to cancel, and as long as the download is still in progress the user can interrupt it. Once the download is completed, there isn't anything left to cancel, so you'd want to unregister it.
This isn't synchronous code you're looking at
This is very much my point - the DownloadStringAsync is _not_ awaited so it’s possible that the download has not even begun, let alone completed, before the callback is unregistered. It won’t wait until the download is complete before unregistering.
Ah, good point!
In fact I think it would just drop out of the enclosing Task completely, ending that thread. I wonder if the download would continue? (So many years of seeing async Task... I didn't even consider the download implementation.)
@bacar that's right. DownloadStringAsync doesn't block the calling thread, so the token registration is disposed immediately after the download has started.
This is very much my point - the
DownloadStringAsyncis _not_ awaited
This method is also confusing (I believe it's in .NET from pre-async/await times). You cannot await it because it returns void:
https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient.downloadstringasync?view=netcore-3.1
The one to await is DownloadStringTaskAsync
@BillWagner @IEvangelist what would be a proper fix here?
Hi friends,
I'd update the code to look like this:
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
class CancelWithCallback
{
static void Main()
{
using var cts = new CancellationTokenSource();
var token = cts.Token;
// Start cancelable task.
_ = Task.Run(async () =>
{
using var client = new WebClient();
client.DownloadStringCompleted += (_, args) =>
{
if (args.Cancelled)
{
Console.WriteLine("The download was canceled.");
}
else
{
Console.WriteLine("The download has completed:\n");
Console.WriteLine($"{args.Result}\n\nPress any key to continue.");
}
};
if (!token.IsCancellationRequested)
{
using CancellationTokenRegistration ctr = token.Register(() => client.CancelAsync());
Console.WriteLine("Starting request\n");
await client.DownloadStringTaskAsync(new Uri("http://www.contoso.com"));
}
}, token);
Console.WriteLine("Press 'c' to cancel.\n\n");
if (Console.ReadKey().KeyChar == 'c')
{
cts.Cancel();
}
Console.WriteLine("\nPress any key to exit.");
Console.ReadKey();
}
}
Changes summarized:
DownloadStringTaskAsync, thanks @pkulikov 👍🏼 Task tusing to CancellationTokenSource and WebClient as both are IDisposableTask.Run(Func<Task>, CancelationToken) overload, exposing async lambdaif logic to avoid negation flowe to args{ occurrences on newlineBut I'll defer to @BillWagner
I like this @IEvangelist Let's do it.