bitbonk
bitbonk

Reputation: 49619

Linking two CancellationTokens without redundant CancellationTokenSources

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();
    }
}

Upvotes: 1

Views: 2106

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456507

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).

Upvotes: 6

Related Questions