I'm working on porting a controller to async. Part of the work involves awaiting an async, cancel-able operation on a disposable object using a cancellation token that's valid for the life of the request. In this particular case it's WebClient.UploadStringTaskAsync(Uri uri, string data)
.
I know the correct way to cancel a WebClient's async operations using cancellationToken.Register(() => webClient.CancelAsync())
. However, the WebClient
is created in a using statement, so it's disposed at the end of the block. As a result, calling webClient.CancelAsync()
from a callback rightfully results in an "Access to disposed closure" warning. I found that CancellationToken.Register(Action callback)
results in a CancellationTokenRegistration
object that implements IDisposable
and unregisters callbacks upon disposal. In the end, my code looks like:
using (var webClient = new WebClient())
using (cancellationToken.Register(() => webClient.CancelAsync())
{
await webClient.UploadStringTaskAsync(uri, data);
}
// cancellationToken can be cancelled later.
I've made a console app to show that code with the same spirit works and cancelling the token after the disposable object and token registration have been disposed doesn't result in the disposable object's callback being called. I want to be certain though: Is this correct and safe?
Edit: I suppose I should rephrase my question a little bit. What's the standard solution for using a cancellation token to cancel an async operation on a disposable object where the operation only supports cancellation through registering a callback?
Is this correct and safe?
Yes.
What's the standard solution for using a cancellation token to cancel an async operation on a disposable object where the operation only supports cancellation through registering a callback?
Ideally, operations take a CancellationToken
directly. In this case, the approach you already have is fine.