When to dispose CancellationTokenSource?
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?
- 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. - Of course you can add
ContinueWith
on task with aDispose
call, but is that the way to go? - 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))
? - 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?
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.
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.
You should always dispose CancellationTokenSource
.
How to dispose it depends exactly on the scenario. You propose several different scenarios.
using
only works when you're usingCancellationTokenSource
on some parallel work that you're waiting. If that's your senario, then great, it's the easiest method.When using tasks, use a
ContinueWith
task as you indicated to dispose ofCancellationTokenSource
.For plinq you can use
using
since you're running it in parallel but waiting on all of the parallel running workers to finish.For UI, you can create a new
CancellationTokenSource
for each cancellable operation that is not tied to a single cancel trigger. Maintain aList<IDisposable>
and add each source to the list, disposing all of them when your component is disposed.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.
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.
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.
It has been a long time since I asked this and got many helpful answers but I came across an interesting issue related to this and thought I would post it here as another answer of sorts:
You should call CancellationTokenSource.Dispose()
only when you are sure that nobody is going to try to get the CTS's Token
property. Otherwise you should not call Dispose()
, because it creates a race condition. For instance, see here:
https://github.com/aspnet/AspNetKatana/issues/108
In the fix for this issue, code which previously did cts.Cancel(); cts.Dispose();
was edited to just do cts.Cancel();
because anyone so unlucky as to try to get the cancellation token in order to observe its cancellation state after Dispose
has been called will unfortunately also need to handle ObjectDisposedException
- in addition to the OperationCanceledException
that they were planning for.
Another key observation related to this fix is made by Tratcher: "Disposal is only required for tokens that won't be cancelled, as cancellation does all of the same cleanup."
i.e. just doing Cancel()
instead of disposing is really good enough!
I wrote a thread-safe class that binds a CancellationTokenSource
to a Task
, and guarantees that the CancellationTokenSource
will be disposed when its associated Task
completes. It uses locks to ensure that the CancellationTokenSource
will not be canceled during or after it has been disposed. This happens for compliance with the documentation, that states:
The
Dispose
method must only be used when all other operations on theCancellationTokenSource
object have completed.
And also:
The
Dispose
method leaves theCancellationTokenSource
in an unusable state.
Here is the CancelableExecution
class:
public class CancelableExecution
{
private readonly bool _allowConcurrency;
private Operation _activeOperation;
// Represents a cancelable operation that signals its completion when disposed
private class Operation : IDisposable
{
private readonly CancellationTokenSource _cts;
private readonly TaskCompletionSource _completionSource;
private bool _disposed;
public Task Completion => _completionSource.Task; // Never fails
public Operation(CancellationTokenSource cts)
{
_cts = cts;
_completionSource = new TaskCompletionSource(
TaskCreationOptions.RunContinuationsAsynchronously);
}
public void Cancel() { lock (this) if (!_disposed) _cts.Cancel(); }
void IDisposable.Dispose() // It is disposed once and only once
{
try { lock (this) { _cts.Dispose(); _disposed = true; } }
finally { _completionSource.SetResult(); }
}
}
public CancelableExecution(bool allowConcurrency)
{
_allowConcurrency = allowConcurrency;
}
public CancelableExecution() : this(false) { }
public bool IsRunning => Volatile.Read(ref _activeOperation) != null;
public async Task<TResult> RunAsync<TResult>(
Func<CancellationToken, Task<TResult>> action,
CancellationToken extraToken = default)
{
ArgumentNullException.ThrowIfNull(action);
CancellationTokenSource cts = CancellationTokenSource
.CreateLinkedTokenSource(extraToken);
using Operation operation = new(cts);
// Set this as the active operation
Operation oldOperation = Interlocked
.Exchange(ref _activeOperation, operation);
try
{
if (oldOperation is not null && !_allowConcurrency)
{
oldOperation.Cancel();
// The Operation.Completion never fails.
await oldOperation.Completion; // Continue on captured context.
}
cts.Token.ThrowIfCancellationRequested();
// Invoke the action on the initial SynchronizationContext.
Task<TResult> task = action(cts.Token);
return await task.ConfigureAwait(false);
}
finally
{
// If this is still the active operation, set it back to null.
Interlocked.CompareExchange(ref _activeOperation, null, operation);
}
// The operation is disposed here, along with the cts.
}
public Task RunAsync(Func<CancellationToken, Task> action,
CancellationToken extraToken = default)
{
ArgumentNullException.ThrowIfNull(action);
return RunAsync<object>(async ct =>
{
await action(ct).ConfigureAwait(false);
return null;
}, extraToken);
}
public Task CancelAsync()
{
Operation operation = Volatile.Read(ref _activeOperation);
if (operation is null) return Task.CompletedTask;
operation.Cancel();
return operation.Completion;
}
public bool Cancel() => CancelAsync().IsCompleted == false;
}
The primary methods of the CancelableExecution
class are the RunAsync
and the Cancel
. By default concurrent (overlapping) operations are not allowed, meaning that calling RunAsync
a second time will silently cancel and await the completion of the previous operation (if it's still running), before starting the new operation.
This class can be used in applications of any kind. Its primary intended usage though is in UI applications, inside forms with buttons for starting and canceling an asynchronous operation, or with a listbox that cancels and restarts an operation every time its selected item is changed. Here is an example of the first use-case:
private readonly CancelableExecution _cancelableExecution = new();
private async void btnExecute_Click(object sender, EventArgs e)
{
string result;
try
{
Cursor = Cursors.WaitCursor;
btnExecute.Enabled = false;
btnCancel.Enabled = true;
result = await _cancelableExecution.RunAsync(async ct =>
{
await Task.Delay(3000, ct); // Simulate some cancelable I/O operation
return "Hello!";
});
}
catch (OperationCanceledException)
{
return;
}
finally
{
btnExecute.Enabled = true;
btnCancel.Enabled = false;
Cursor = Cursors.Default;
}
this.Text += result;
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cancelableExecution.Cancel();
}
The RunAsync
method accepts an extra CancellationToken
as argument, that is linked to the internally created CancellationTokenSource
. Supplying this optional token may be useful in advanced scenarios.
For a version compatible with the .NET Framework, you can look at the 3rd revision of this answer.
精彩评论