Correct pattern to dispose of cancellation token source
To ensure that a CTS (CancellationTokenSource
) associated with a fire-and-forget Task
will be eventually disposed, you should attach a continuation to the task, and dispose the CTS from inside the continuation. This creates a problem though, because another thread could call the Cancel
method while the object is in the midst of its disposal, and according to the documentation the Dispose
method is not thread-safe:
All public and protected members of
CancellationTokenSource
are thread-safe and may be used concurrently from multiple threads, with the exception ofDispose()
, which must only be used when all other operations on theCancellationTokenSource
object have completed.
So calling Cancel
and Dispose
from two different threads concurrently without synchronization is not an option. This leaves only one option available: to add a layer of synchronization around all public members of the CTS class. This is not a happy option though, for several reasons:
- You must write the thread-safe wrapper class (write code)
- You must use it every time you start a cancelable fire-and-forget task (write more code)
- Incur the performance penalty of the synchronization
- Incur the performance penalty of the attached continuations
- Having to maintain a system that has become more complex and more bug-prone
- Having to cope with the philosophical question why the class was not designed to be thread-safe in the first place
So my recommendation is to do the alternative, which is simply to leave the CTS undisposed, only in these cases where you can't await the completion of its associated tasks. In other words if it's not possible to enclose the code that uses the CTS in a using
statement, just let the garbage collector to do the reclaiming of the reserved resources. This means that you'll have to disobey this part of the documentation:
Always call Dispose before you release your last reference to the
CancellationTokenSource
. Otherwise, the resources it is using will not be freed until the garbage collector calls theCancellationTokenSource
object'sFinalize
method.
...and this:
The
CancellationTokenSource
class implements theIDisposable
interface. You should be sure to call theCancellationTokenSource.Dispose
method when you have finished using the cancellation token source to free any unmanaged resources it holds.
If this makes you feel a bit dirty, you are not alone. You may feel better if you think that the Task
class implements the IDisposable
interface too, but disposing task instances is not required.
The correct practice is second - you dispose of the CancellationTokenSource
after you are sure the task is cancelled. CancellationToken
relies on information from CancellationTokenSource
to function properly. While the current implementation CancellationToken
is written in such a way that is will still work even without throwing exceptions if the CTS it was created from is disposed, it may not behave properly or always as expected.