When to dispose CancellationTokenSource?

64,005

Solution 1

Speaking about whether it's really necessary to call Dispose on CancellationTokenSource... I had a memory leak in my project and it turned out that CancellationTokenSource was the problem.

My project has a service, that is constantly reading database and fires off different tasks, and I was passing linked cancellation tokens to my workers, so even after they had finished processing data, cancellation tokens weren't disposed, which caused a memory leak.

MSDN Cancellation in Managed Threads states it clearly:

Notice that you must call Dispose on the linked token source when you are done with it. For a more complete example, see How to: Listen for Multiple Cancellation Requests.

I used ContinueWith in my implementation.

Solution 2

I didn't think any of the current answers were satisfactory. After researching I found this reply from Stephen Toub (reference):

It depends. In .NET 4, CTS.Dispose served two primary purposes. If the CancellationToken's WaitHandle had been accessed (thus lazily allocating it), Dispose will dispose of that handle. Additionally, if the CTS was created via the CreateLinkedTokenSource method, Dispose will unlink the CTS from the tokens it was linked to. In .NET 4.5, Dispose has an additional purpose, which is if the CTS uses a Timer under the covers (e.g. CancelAfter was called), the Timer will be Disposed.

It's very rare for CancellationToken.WaitHandle to be used, so cleaning up after it typically isn't a great reason to use Dispose. If, however, you're creating your CTS with CreateLinkedTokenSource, or if you're using the CTS' timer functionality, it can be more impactful to use Dispose.

The bold part I think is the important part. He uses "more impactful" which leaves it a bit vague. I'm interpreting it as meaning calling Dispose in those situations should be done, otherwise using Dispose is not needed.

Solution 3

You should always dispose CancellationTokenSource.

How to dispose it depends exactly on the scenario. You propose several different scenarios.

  1. using only works when you're using CancellationTokenSource on some parallel work that you're waiting. If that's your senario, then great, it's the easiest method.

  2. When using tasks, use a ContinueWith task as you indicated to dispose of CancellationTokenSource.

  3. For plinq you can use using since you're running it in parallel but waiting on all of the parallel running workers to finish.

  4. For UI, you can create a new CancellationTokenSource for each cancellable operation that is not tied to a single cancel trigger. Maintain a List<IDisposable> and add each source to the list, disposing all of them when your component is disposed.

  5. For threads, create a new thread that joins all the worker threads and closes the single source when all of the worker threads finished. See CancellationTokenSource, When to dispose?

There's always a way. IDisposable instances should always be disposed. Samples often don't because they're either quick samples to show core usage or because adding in all aspects of the class being demonstrated would be overly complex for a sample. The sample is just that a sample, not necessarily (or even usually) production quality code. Not all samples are acceptable to be copied into production code as is.

Solution 4

I took a look in ILSpy for the CancellationTokenSource but I can only find m_KernelEvent which is actually a ManualResetEvent, which is a wrapper class for a WaitHandle object. This should be handled properly by the GC.

Solution 5

This answer is still coming up in Google searches, and I believe the voted up answer does not give the full story. After looking over the source code for CancellationTokenSource (CTS) and CancellationToken (CT) I believe that for most use cases the following code sequence is fine:

if (cancelTokenSource != null)
{
    cancelTokenSource.Cancel();
    cancelTokenSource.Dispose();
    cancelTokenSource = null;
}

The m_kernelHandle internal field mentioned above is the synchronization object backing the WaitHandle property in both the CTS and CT classes. It is only instantiated if you access that property. So, unless you are using WaitHandle for some old-school thread synchronization in your Task calling dispose will have no effect.

Of course, if you are using it you should do what is suggested by the other answers above and delay calling Dispose until any WaitHandle operations using the handle are complete, because, as is described in the Windows API documentation for WaitHandle, the results are undefined.

Share:
64,005
George Mamaladze
Author by

George Mamaladze

Updated on March 12, 2021

Comments

  • George Mamaladze
    George Mamaladze about 3 years

    The class CancellationTokenSource is disposable. A quick look in Reflector proves usage of KernelEvent, a (very likely) unmanaged resource. Since CancellationTokenSource has no finalizer, if we do not dispose it, the GC won't do it.

    On the other hand, if you look at the samples listed on the MSDN article Cancellation in Managed Threads, only one code snippet disposes of the token.

    What is the proper way to dispose of it in code?

    1. You cannot wrap code starting your parallel task with using if you do not wait for it. And it makes sense to have cancellation only if you do not wait.
    2. Of course you can add ContinueWith on task with a Dispose call, but is that the way to go?
    3. What about cancelable PLINQ queries, which do not synchronize back, but just do something at the end? Let's say .ForAll(x => Console.Write(x))?
    4. Is it reusable? Can the same token be used for several calls and then dispose it together with the host component, let's say UI control?

    Because it does not have something like a Reset method to clean-up IsCancelRequested and Token field I would suppose it's not reusable, thus every time you start a task (or a PLINQ query) you should create a new one. Is it true? If yes, my question is what is the correct and recommended strategy to deal with Dispose on those many CancellationTokenSource instances?

  • George Mamaladze
    George Mamaladze over 12 years
    I have the same feeling that GC will cleanup that all. I'll try to verify that. Why do Microsoft implemented dispose in this case? To get rid of event callbacks and avoid propagation to second generation GC probably. In this case calling Dispose is optional - call it if you can, if not just ignore it. Not the best manner I think.
  • George Mamaladze
    George Mamaladze over 12 years
    I have investigated this issue. CancellationTokenSource gets garbage collected. You might help with dispose to do it in GEN 1 GC. Accepted.
  • stijn
    stijn about 10 years
    for point 2, any reason you could not use await on the task and dispose the CancellationTokenSource in the code that comes after the await?
  • Mike Strobel
    Mike Strobel almost 10 years
    There are caveats. If the CTS gets canceled while you await an operation, you may resume due to an OperationCanceledException. You might then call Dispose(). But if there are operations still running and using the corresponding CancellationToken, that token still reports CanBeCanceled as being true even though the source is disposed. If they attempt to register a cancellation callback, BOOM!, ObjectDisposedException. It's safe enough to call Dispose() after successful completion of the operation(s). It gets really tricky when you actually need to cancel something.
  • herzbube
    herzbube about 9 years
    The MSDN article Cancellation in Managed Threads states: "Listeners monitor the value of the IsCancellationRequested property of the token by polling, callback, or wait handle." In other words: It may not be you (i.e. the one making the async request) who uses the wait handle, it may be the listener (i.e. the one answering the request). Which means you, as the one responsible for disposing, effectively have no control over whether the wait handle is used or not.
  • Joe Amenta
    Joe Amenta almost 9 years
    I did this same investigation independently and came to the same conclusion: dispose if you easily can, but don't fret over trying to do so in the rare-but-not-unheard-of cases where you've sent a CancellationToken out into the boondocks and don't want to wait for them to write a postcard back telling you they're done with it. This is going to happen every now and then because of the nature of what CancellationToken is used for, and it's really OK, I promise.
  • Søren Boisen
    Søren Boisen almost 9 years
    Downvoted for the reasons given by Mike Strobel - forcing a rule to always call Dispose can get you into hairy situations when dealing with CTS and Task due to their asynchronous nature. The rule should instead be: always dispose linked token sources.
  • Søren Boisen
    Søren Boisen almost 9 years
    This is an important omission in the current accepted answer by Bryan Crosby - if you create a linked CTS, you risk memory leaks. The scenario is very similar to event handlers that are never unregistered.
  • Joe Amenta
    Joe Amenta almost 9 years
    My above comment doesn't apply to linked token sources; I couldn't prove that it's OK to leave these undisposed, and the wisdom in this thread and MSDN suggests that it might not be.
  • Joseph Lennox
    Joseph Lennox over 8 years
    According to MSDN, registered callbacks that have exceptioned will cause .Cancel to throw. Your code will not call .Dispose() if this happens. The callbacks should be careful not to do this, but it can happen.
  • BitMask777
    BitMask777 about 8 years
    I had a leak due to this same issue. Using a profiler I could see callback registrations holding references to the linked CTS instances. Examining the code for the CTS Dispose implementation here was very insightful, and underscores @SørenBoisen comparison to event handler registration leaks.
  • George Mamaladze
    George Mamaladze about 8 years
    Comments above reflect the discussion state were the other answer by @Bryan Crosby was accepted.
  • Grigory
    Grigory almost 8 years
    More impactful means that child CTS is added to parent one. If you don't dispose child there will be a leak if parent is long-living. So it is critical to dispose linked ones.
  • Trisped
    Trisped about 5 years
    Your link goes to a deleted answer.
  • Endrju
    Endrju about 4 years
    The documentation in 2020 clearly says: Important: The CancellationTokenSource class implements the IDisposable interface. You should be sure to call the CancellationTokenSource.Dispose method when you have finished using the cancellation token source to free any unmanaged resources it holds. - docs.microsoft.com/en-us/dotnet/standard/threading/…
  • Shadow
    Shadow over 2 years
    And you never know when token you passed somewhere will be linked to
  • Glenn Slayden
    Glenn Slayden about 2 years
    For more on the conditions @MikeStrobel describes, see this concise summary of the various CTS perils. Also, for the record, if such issues apply to your code base but you still insist on disposing every CTS, the neccessary approach/fix would be to manually unlink the registrations and detach any timers prior to disposing the CTS. Beware, though, you might be in for it.
  • Glenn Slayden
    Glenn Slayden about 2 years
    There's also some good information in a code comment from the latest .NET source of CancellationTokenSource.
  • JamesUsedHarden
    JamesUsedHarden about 2 years
    @glenslayden thanks! Looks live the implementation of Dispose changed a lot since my original answer and it's even more important to call Dispose now.