Consider a scenario where you have some asynchronous work to be done and you can run it in a fire and forget mode. This asynchronous work is able to listen for cancellation and so you pass it a cancellation token in order to being able to cancel it.
At a given moment in time we can decide to request the cancellation of the ongoing activity, by using the cancellation token source object from which we have taken the cancellation token.
Because cancellation token source implements IDisposable
, we should call its Dispose
method as far as we are done with it. The point of this question is determining exactly when you are done with a given cancellation token source.
Let's suppose that you decide to cancel the ongoing work by calling the Cancel
method on the cancellation token source: is it necessary to wait for the completion of the ongoing operation before calling Dispose
?
Put in other words, should I do this way:
class Program
{
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
var token = cts.Token;
DoSomeAsyncWork(token); // starts the asynchronous work in a fire and forget manner
// do some other stuff here
cts.Cancel();
cts.Dispose(); // I call Dispose immediately after cancelling without waiting for the completion of ongoing work listening to the cancellation requests via the token
// do some other stuff here not involving the cancellation token source because it's disposed
}
async static Task DoSomeAsyncWork(CancellationToken token)
{
await Task.Delay(5000, token).ConfigureAwait(false);
}
}
or this way:
class Program
{
static async Task Main(string[] args)
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var task = DoSomeAsyncWork(token); // starts the asynchronous work in a fire and forget manner
// do some other stuff here
cts.Cancel();
try
{
await task.ConfigureAwait(false);
}
catch(OperationCanceledException)
{
// this exception is raised by design by the cancellation
}
catch (Exception)
{
// an error has occurred in the asynchronous work before cancellation was requested
}
cts.Dispose(); // I call Dispose only when I'm sure that the ongoing work has completed
// do some other stuff here not involving the cancellation token source because it's disposed
}
async static Task DoSomeAsyncWork(CancellationToken token)
{
await Task.Delay(5000, token).ConfigureAwait(false);
}
}
Additional details: the code I'm referring to is written inside an ASP.NET core 2.2 web application, here I'm using a console application scenario just to simplify my example.
I've found similar questions on stackoverflow asking for the need to dispose of cancellation token sources objects. Some of the answers suggest that in some scenario disposing of this object is not really needed.
My approach to the whole IDisposable
subject is that I always tend to adhere to the exposed contract of a class, put another way if an object claims to be disposable I prefer to always call Dispose
when I'm done with it. I don't like the idea of guessing whether or not calling dispose is really required by depending on implementation details of the class that could change in a future release in an undocumented manner.
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 the CancellationTokenSource object's Finalize method.
How do you handle a CancellationToken? Since cancellations are cooperative, CancellationToken handling is left to the method it is passed to. Generally, we either pass the CancellationToken to other methods or stop as soon as possible once the token is canceled.
Operation Cancellation Versus Object Cancellation After the IsCancellationRequested property of the token has been set to true , it cannot be reset to false . Therefore, cancellation tokens cannot be reused after they have been canceled.
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:
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.
Like any IDisposable
you dispose it when you're done with the resource. That's the hard rule of IDisposable
and i haven't encountered a situation where that was not the case but i am of course open to learning ;).
In case of the CancellationTokenSource
this means that you dispose the source when both the object itself and the Token
property are no longer used.
(I just had a source open for this statement but alas i got distracted and lost it somehow)
So you dispose when tasks are no longer using the CancellationToken
. In your case, the second option, since you then are sure that no tasks use the token.
edit; adding to this, its good practice to also set any properties to null that implement disposable. in this case since you only have local variables this does not matter, but when you have the token source as a field or something, make sure to set the field to null so that there are no references to the token source.
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