【问题标题】:How to Create a Hierarchical CancellationTokenSource?如何创建分层 CancellationTokenSource?
【发布时间】:2021-11-29 14:56:53
【问题描述】:

在我们的项目中,我们决定在CancellationToken的帮助下为用户提供取消机制。

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

.NET 中是否有现成可用的选项?如果没有,我不确定向父代币注册代表是否足够,或者应该给予进一步考虑。

【问题讨论】:

    标签: c# task task-parallel-library cancellation


    【解决方案1】:

    基于the source codeLinked2CancellationTokenSource的实现,我来到了这个实现:

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

    还有一个演示:

    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 =&gt; ((CancellationTokenSource)s).Cancel(false), this, false); - 它避免为每个注册创建 2 个额外的对象(捕获上下文实例和委托实例) - 相反,实例 (this) 作为状态传递,并且委托将通过编译器生成的静态字段被提升和重用
    • @MarcGravell 感谢您的解释。我编辑了我的答案。
    【解决方案2】:

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

    创建一个CancellationTokenSource,当任何源令牌处于取消状态时,该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
    

    【讨论】:

    • 感谢您的回答。是否可以扩展CancellationTokenSource 以在内部支持这种行为?看来TokenCancel 是不可覆盖的。
    • @momt99 为什么要覆盖它们?你想在那里实现什么,但还不能实现?
    • @momt99 不,CancellationTokenSource 类的唯一可覆盖成员是 Dispose(bool disposing) 方法。
    • @MarcGravell 我希望扩展 CancellationTokenSource 以提供这样的行为。
    • @momt99 啊,你在颠倒父/子方向;很好 - 如果这对你有用:很好
    猜你喜欢
    • 2017-12-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-01
    • 2013-05-07
    • 1970-01-01
    • 1970-01-01
    • 2018-07-09
    相关资源
    最近更新 更多