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.
*/
this._parentReg = parentToken.Register(static s => ((CancellationTokenSource)s).Cancel(false), this, false);
- it avoids creating 2 additional objects per registration (the capture context instance, and a delegate instance) - instead, the instance (this
) is passed as state, and the delegate will be hoisted and reused via a compiler-generated static field – Transcend