Create a long-lived "var source1 = new CancellationTokenSource();", and a shorter-lived "var source2 = new CancellationTokenSource();". Create a linked CTS from the two: "var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token);"
For an example of why you might want to do this, consider a Windows application with a window containing several panes, each running independent async work, and each with its own "Stop" button. The window can own "source1" and Cancel it when the window is closed. Each pane can have its own "source2", and Cancel it when "Stop" is clicked. The work started by each pane can be given "linkedSource.Token" as its cancellation token, and it will be cancelled when the window is closed or the pane's "Stop" button is clicked. (Maybe there's a better design, and I'm interested in feedback on that, but this is primarily about what seems to be a bug in TPL under this scenario.)
As per http://msdn.microsoft.com/en-us/library/dd321505.aspx, a CancellationTokenSource object should be disposed when you're finished with it. For the Windows application mentioned above, consider the scenario when all work in one of the panes has finished, and maybe the pane has been removed from the UI. Failing to dispose "source2" and "linkedSource" (for that pane) will "leak" both those CTS objects until "source1" is GCed. (According to .NET Memory Profiler 18.104.22.168, "source1" holds a hard reference to "source2" and "linkedSource" via m_registeredCallbacksLists and m_linkingRegistrations.) In the Windows application, this could be a problem if the window is long-lived, and lots of panes are created and removed over time.
If just "source2" is disposed, both "source2" and "linkedSource" will still be "leaked".
If "linkedSource" is disposed first, then "source2" is disposed, both objects will be cleaned up (as expected).
However, if "source2" is disposed first and then "linkedSource" is disposed, calling "linkedSource.Dispose()" will throw an ObjectDisposedException. The callstack in the exception object is:
This appears to violate the guidelines for Dispose (http://msdn.microsoft.com/en-us/library/system.idisposable.dispose.aspx): "If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times."
However, if you catch and ignore the ObjectDisposedException, then both "source2" and "linkedSource" are deregistered properly and GCed.
Thus, in order for linked CancellationTokenSource objects to be cleaned up properly, you have to:
A) Dispose all the CTS objects in a precise order, or
B) Dispose the CTS objects in any order, but catch and ignore all ObjectDisposedExceptions thrown while doing so.
It should be legal to dispose CTS objects in any order.
(Please let me know if I should file a Connect issue on this bug.)
Thursday, December 01, 2011 6:50 PM
- Edited by Bradley Grainger Thursday, December 01, 2011 9:57 PM
Thank you for taking the time to write-up all of those details. This was fixed for .NET 4.5... you can dispose of the tokens in any order, you can call dispose multiple times, and no exception will be thrown. If you have some time to try with the .NET 4.5 Developer Preview that was released in September, you shouldn't run into this problem... if you do, please do let us know and open a Connnect issue for the bug.
Thanks!Thursday, December 01, 2011 10:48 PMOwner