When to Dispose Cancellationtokensource

When to dispose CancellationTokenSource?

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.

Correct pattern to dispose of cancellation token source

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.

How to implement cancellation and dispose a CancellationTokenSource correctly

It is not possible to tell where the error is originated from the code you have posted. Generally, you must inspect the eception message (callstack) to know where exactly the exception was triggered.

Call Dispose once cancellation has been requested or the cancellable operations have completed. The exception is thrown when you access the mutating members of CancellationTokenSource or its CancellationToken instance, when the CancellationTokenSource was exposed. Like when calling Cancel on the disposed instance or trying to get the reference to the associated CancellationToken after Dispose was called. You must ensure that no code accesses the disposed instance.

You can do this by setting the CancellationTokenSource property to null when disposing and by adding a null check before accessing the CancellationTokenSource. You must control the lifetime of the CancellationTokenSource carefully.

The following example shows how to control the lifetime of the CancellationTokenSource and guard against illegal referencing of the disposed instance:

private CancellationTokenSource CancellationtokenSource { get; set; }

private void CancelCancellableOperation_OnClick(object sender, EventArgs e)
{
// Check for null to avoid an ObjectDisposedException
// (or a NullReferenceException in particular) exception.
// The implemented pattern sets the property to null immediately after disposal (not thread-safe).
this.CancellationTokenSource?.Cancel();
}

// Start scope of CancellationTokenSource.
// Lifetime is managed by a try-catch-finally block and the use of
// CancellationToken.ThrowIfCancellationRequested
// to forcefully enter the try-catch-finally block on cancellation.
private async Task DoWorkAsync()
{
this.CancellationTokenSource = new CancellationTokenSource();
try
{
await CancellableOperationAsync(this.CancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// Do some cleanup or rollback.
// At this point the CancellationTokenSource is still not disposed.
}
finally
{
// Invalidate CancellationTokenSource property to raise an NullReferenceException exception
// to indicate that thet access ocurred uncontrolled and requires a fix.
// Create a local copy of the property to avoid race conditions.
var cancellationTokenSource = this.CancellationTokenSource;
this.CancellationTokenSource = null;

// Dispose after cancellation
// or cancellable operations are completed
cancellationTokenSource.Dispose();
}
}

private async Task CancellableOperationAsync(CancellationToken cancellationToken)
{
// Guarantee that CancellationTokenSource is never disposed before
// CancellationTokenSource.Cancel was called or the cancellable operation has completed

// Do something
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(10));

// Add null check if you can't guard against premature disposal
cancellationToken?.ThrowIfCancellationRequested();
}
}

is it necessary to cancel cancellationtoken before disposal

After a CancellationTokenSource has been disposed tokens based on this source may throw ObjectDisposedException so you should not use CancellationTokenSource.Token after the source has been disposed. Fortunately, I don't see this happening in your code.

When you cancel a CancellationTokenSource it changes state and notifies callbacks that have been registered for the token. However, when your code is about to dispose the CancellationTokenSource you are already done using the token and there is no need to cancel it.

So in your case it is not necessary to cancel the CancellationTokenSource before disposing it. However, your use case is somewhat special. When you have a background task you should wait for the task to complete before disposing the source (as stated in my initial paragraph):

using (var cts = new CancellationTokenSource()) {
var task = Task.Run(() => DoSomething(cts.Token));
// Cancel cts or let it cancel itself based on a timeout.
// Then wait for the task to end.
await task;
}

Calling cancellationToken.Cancel() in Dispose of Controller?


Does disposing the Cancellation token in Controller.Dispose() causes
the long running task to cancel?

Depends on how your longRunningTask was implemented.
In this method you should explicitly check whether the cancellation is requested:

token.ThrowIfCancellationRequested();

After invoking of this method your task will be cancelled.

Cancellation example

If in the following example the ThrowIfCancellationRequested would not be invoked, the task would be run forever:

var cts = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
cts.Token.ThrowIfCancellationRequested();
}, cts.Token);

You can learn more about cancellation here.


Note that after setting the cancellationTokenSource to null, you can get NullReferenceException in your foreach loop. I would suggest to copy your token into a local variable:

public async Task<HttpResponseMessage> Post(SomeData data)
{
var token = cancellationTokenSource.Token;
foreach (var item in data)
{
await longRunningTask(item, token);
}
}


Related Topics



Leave a reply



Submit