如何创建分层 CancellationTokenSource?

mom*_*t99 7 c# task task-parallel-library cancellation

在我们的项目中,我们决定借助CancellationToken.

由于项目中作品的结构,我需要一个分层取消机制。通过分层,我的意思是父源取消会导致所有子源被递归取消,但子源取消不会传播到父源。

.NET 中是否有开箱即用的此类选项?如果没有,我不确定向父令牌注册委托是否足够,或者应该进一步考虑。

The*_*ias 10

是的,此功能开箱即用。查看CancellationTokenSource.CreateLinkedTokenSource方法。

创建CancellationTokenSource当任何源令牌处于取消状态时将处于取消状态的 。

例子:

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
Run Code Online (Sandbox Code Playgroud)


mom*_*t99 3

根据源代码中的实现Linked2CancellationTokenSource,我得出了这样的实现:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一个演示:

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.
*/
Run Code Online (Sandbox Code Playgroud)

  • 建议:尝试 `this._parentReg = ParentToken.Register(static s =&gt; ((CancellationTokenSource)s).Cancel(false), this, false);` - 它避免每次注册创建 2 个额外的对象(捕获上下文实例,以及委托实例) - 相反,实例(`this`)作为状态传递,委托将通过编译器生成的静态字段提升和重用 (2认同)