I've been seeing code that uses Cancellation.Register
with a using
clause on the CancellationTokenRegistration
result:
using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync()))
{
await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}
I get that you should make sure you Dispose
an IDisposable
, but why does it even implements IDisposable
? what resources does it have to release? The only methods it has regard equality.
What happens if you don't Dispose
of it? what do you leak?
This pattern is a convenient way to make sure CancellationTokenRegistration.Unregister()
is called automatically. It's often used by Stephen Toub in his Parallel Programming with .NET blog posts, e.g. here.
I get that you should make sure you Dispose an IDisposable, but why does it even implements IDisposable? what resources does it have to release? The only methods it has regard equality.
IMO, the best answer to this can be found in the .NET 4 Cancellation Framework post by Microsoft's Mike Liddell:
When a callback is registered to a
CancellationToken
, the current thread'sExecutionContext
is captured so that the callback will be run with the the exact same security context . The capturing of the current thread's synchronization context is optional can be requested via an overload ofct.Register()
if required. Callbacks are normally stored and then run when cancellation is requested, but if a callback is registered after cancellation has been requested, the callback will run immediately on the current thread, or viaSend()
on the currentSynchronizationContext
if applicable.When a callback is registered to a
CancellationToken
, the returned object is aCancellationTokenRegistration
. This is a light struct type that isIDiposable
, and disposing this registration object causes the callback to be deregistered. A guarantee is made that after theDispose()
method has returned, the registered callback is neither running nor will subsequently commence. A consequence of this is thatCancellationTokenRegistration.Dispose()
must block if the callback is currently executing. Hence, all registered callbacks should be fast and not block for any significant duration.
Another relevant document by Mike Liddell is "Using Cancellation Support in .NET Framework 4" (UsingCancellationinNET4.pdf).
Updated, this is verifiable here in the Reference Source.
It's also important to note, the cancellation callback is registered with the CancellationTokenSource
, not with CancellationToken
. So, if CancellationTokenRegistration.Dispose()
is not correctly scoped, the registration will remain active for the lifetime of the parent CancellationTokenSource
object. This may lead to an unexpected callback when the scope of the async operation is over, e.g.:
async Task TestAsync(WebClient wc, CancellationToken token)
{
token.Register(() => wc.CancelAsync());
await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}
// CancellationTokenSource.Cancel() may still get called later,
// in which case wc.CancelAsync() will be invoked too
Thus, it's important to scope the disposable CancellationTokenRegistration
with using
(or call CancellationTokenRegistration.Dispose()
explicitly with try/finally
).
why does it even implements IDisposable? what resources does it have to release?
IDisposable
is often used for things completely unrelated to releasing resources; it's a convenient way to ensure something will be done at the end of a using
block, even if an exception occurs. Some people (not me) argue that doing this is an abuse of the Dispose
pattern.
In the case of CancellationToken.Register
, the "something" is the unregistration of the callback.
Note that in the code you posted in your question, the use of a (EDIT: not true anymore since the question was edited)using
block on the CancellationTokenRegistration
is almost certainly a mistake: since wc.DownloadStringAsync
returns immediately, the callback will be unregistered immediately, before the operation has a chance to be cancelled, so wc.CancelAsync
will never be called, even if the CancellationToken
is signaled. It would make sense if the call to wc.DownloadStringAsync
was awaited, because in that case the end of the using
block wouldn't be reached before the completion of wc.DownloadStringAsync
.
What happens if you don't Dispose of it? what do you leak?
In this case, what happens is that the callback is not unregistered. It probably doesn't really matter, because it's only referenced by the cancellation token, and since EDIT: actually, that's not correct; see Noseratio's answer for an explanationCancellationToken
is a value type that is typically stored only on the stack, the reference will disappear when it goes out of scope. So it won't leak anything in most cases, but it might if you store the CancellationToken
somewhere on the heap.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With