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.IMO, the best answer to this can be found in the .NET 4 Cancellation Framework post by Microsoft's Mike Liddell:
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 withCancellationToken
. So, ifCancellationTokenRegistration.Dispose()
is not correctly scoped, the registration will remain active for the lifetime of the parentCancellationTokenSource
object. This may lead to an unexpected callback when the scope of the async operation is over, e.g.:Thus, is important to scope the disposable
CancellationTokenRegistration
withusing
(or callCancellationTokenRegistration.Dispose()
explicitly withtry/finally
).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 ausing
block, even if an exception occurs. Some people (not me) argue that doing this is an abuse of theDispose
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 theCancellationTokenRegistration
is almost certainly a mistake: sincewc.DownloadStringAsync
returns immediately, the callback will be unregistered immediately, before the operation has a chance to be cancelled, sowc.CancelAsync
will never be called, even if theCancellationToken
is signaled. It would make sense if the call towc.DownloadStringAsync
was awaited, because in that case the end of theusing
block wouldn't be reached before the completion ofwc.DownloadStringAsync
.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 sinceEDIT: 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 theCancellationToken
somewhere on the heap.