momvart
momvart

Reputation: 1999

How to Create a Hierarchical CancellationTokenSource?

In our project, we have decided to provide a cancellation mechanism for users with the help of CancellationToken.

Because of the structure of the works in the project, I need a hierarchical cancellation mechanism. By hierarchical, I mean that parent source cancellation causes all child sources to be recursively canceled but child sources cancellations are not propagated to the parent.

Is there such an option available in .NET out of the box? If not, I'm not sure whether registering a delegate to the parent token is enough or further considerations should be given.

Upvotes: 8

Views: 853

Answers (2)

momvart
momvart

Reputation: 1999

Based on the implementation in the source code for Linked2CancellationTokenSource, I came to this implementation:

public class HierarchicalCancellationTokenSource : CancellationTokenSource
{
    private readonly CancellationTokenRegistration _parentReg;

    public HierarchicalCancellationTokenSource(CancellationToken parentToken)
    {
        this._parentReg = parentToken.Register(
            static s => ((CancellationTokenSource)s).Cancel(false),
            this,
            useSynchronizationContext: false);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this._parentReg.Dispose();
        }

        base.Dispose(disposing);
    }
}

And a demo:

CancellationTokenSource[] CreateChildSources(CancellationTokenSource parentSource) =>
    Enumerable.Range(0, 2)
        .Select(_ => new HierarchicalCancellationTokenSource(parentSource.Token))
        .ToArray();

var rootSource = new CancellationTokenSource();
var childSources = CreateChildSources(rootSource);
var grandChildSources = childSources.SelectMany(CreateChildSources).ToArray();

var allTokens = new[] { rootSource.Token }
    .Concat(childSources.Select(s => s.Token))
    .Concat(grandChildSources.Select(s => s.Token))
    .ToArray();

for (int i = 0; i < allTokens.Length; i++)
{
    allTokens[i].Register(
        i => Console.WriteLine(
            $"{new string('+', (int)Math.Log2((int)i))}{i} canceled."),
        i + 1);
}

rootSource.Cancel();

/* Output:
1 canceled.
+3 canceled.
++7 canceled.
++6 canceled.
+2 canceled.
++5 canceled.
++4 canceled.
*/

Upvotes: 3

Theodor Zoulias
Theodor Zoulias

Reputation: 43515

Yes, this functionality exists out of the box. Check out the CancellationTokenSource.CreateLinkedTokenSource method.

Creates a CancellationTokenSource that will be in the canceled state when any of the source tokens are in the canceled state.

Example:

using var parentCts = new CancellationTokenSource();
using var childrenCts = CancellationTokenSource
    .CreateLinkedTokenSource(parentCts.Token);

parentCts.Cancel(); // Cancel the children too
childrenCts.Cancel(); // Doesn't affect the parent

Upvotes: 11

Related Questions