1

Inside a method that gets an CancellationToken (StartAsync) I would like to add an internal CancellationToken so the asynchronous operation can either be cancelled by the caller externally or internally (e.g. by calling an AbortAsync() method).

AFAIK, the way to do it is to use CreateLinkedCancellationTokenSource. But its APIs seems to be rather uncomfortable because I need to create two additional CancellationTokenSource instance for this and because they implement IDisposable, I must also not forget to dispose them. As a result, I need store both of them as members for later disposal.

Am I missing something? I feel there should be an easier way to attach an additional cancellation mechanism to an existing token that doesn't force me to maintain two CancellationTokenSource instances.

public Task StartAsync(CancellationToken externalToken)
{
    this.actualCancellation = new CancellationTokenSource();
    this.linkedCancellation = CancellationTokenSource.CreateLinkedTokenSource(
        actualCancellation.Token, externalToken);
    this.execution = this.ExecuteAsync(this.linkedCancellation.Token);
    return this.execution;
}

public async Task AbortAsync()
{
    try
    {
        this.actualCancellation.Cancel();
        await this.execution;
    }
    catch
    {
    }
    finally
    {
        this.actualCancellation.Dispose();
        this.linkedCancellation.Dispose();
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
bitbonk
  • 48,890
  • 37
  • 186
  • 278
  • As a side note, disposing a `CancellationTokenSource` is probably not absolutely critical. [Here](https://stackoverflow.com/questions/61359443/correct-pattern-to-dispose-of-cancellation-token-source/61361371#61361371) are my thoughts about this. Some people have reported [memory leaks](https://stackoverflow.com/questions/6960520/when-to-dispose-cancellationtokensource#comment49913373_12474762) caused by undisposed CTSs, but I haven't managed to reproduce it. – Theodor Zoulias May 04 '20 at 17:19

1 Answers1

5

A linked cancellation source is not a special kind of cancellation source. It's a regular cancellation source that is also "linked" to an existing token - i.e., the source will be cancelled when the existing token is cancelled. In all other respects, it's a normal cancellation source, so you can cancel it yourself just like any other cancellation source.

So you only need one cancellation source - one that is linked to the existing token and can also be cancelled manually:

public Task StartAsync(CancellationToken externalToken)
{
    this.linkedCancellation = CancellationTokenSource.CreateLinkedTokenSource(externalToken);
    this.execution = this.ExecuteAsync(this.linkedCancellation.Token);
    return this.execution;
}

public async Task AbortAsync()
{
    try
    {
        this.linkedCancellation.Cancel();
        await this.execution;
    }
    catch
    {
    }
    finally
    {
        this.linkedCancellation.Dipose();
    }
}

Just as a side note, I'd carefully consider lifetime issues with this kind of API design. Currently the StartAsync does resource allocation and AbortAsync does cleanup; I'd recommend a design where these are handled by the constructor and Dispose (RAII).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • At once point I had my code exactly as yours and I somehow assumed that it does not work. Thanks! – bitbonk May 04 '20 at 13:50
  • 3
    The [`CancellationTokenSource.CreateLinkedTokenSource`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource) method has two overloads, one with two tokens as arguments and a second one with a `params` array of tokens. Surprisingly the seemingly most useful overload that would accept a single token is missing. No wonder that the misconception "at least two tokens are needed for a linked source" has been formed inside some people's minds (myself included). – Theodor Zoulias May 04 '20 at 17:07
  • 1
    @TheodorZoulias Apparently they've realized this mistake and added another overload in .NET 5 https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.createlinkedtokensource?view=net-5.0#System_Threading_CancellationTokenSource_CreateLinkedTokenSource_System_Threading_CancellationToken_ – mono blaine Dec 27 '20 at 19:03