【问题标题】:Does linking keep the linked block alive?链接是否使链接块保持活动状态?
【发布时间】:2018-03-22 18:16:14
【问题描述】:

当使用System.Threading.Tasks.Dataflow时,如果我将块a链接到块b,链接会保持b处于活动状态吗?还是我需要保留对b 的引用以防止它被收集?

internal class SomeDataflowUser
{
    public SomeDataflowUser()
    { 
        _a = new SomeBlock();
        var b = new SomeOtherBlock();
        _a.LinkTo(b);
    }

    public void ReactToIncomingMessage( Something data )
    {    
        // might b be collected here?
        _a.Post( data );
    }

    private ISourceBlock<Something> _a;
}

【问题讨论】:

  • 有什么东西让a 活着吗? ab 在这里似乎是未初始化的局部变量;这些是什么?您是否有某些理由不相信垃圾收集器能够正确完成其工作?你到底想问什么?
  • @Eric:我的问题是关于数据流的内部工作原理:链接会阻止垃圾收集吗?
  • @Lasse: "link a to b" 表示在a 上调用LinkTo 参数b
  • 那些是托管对象。垃圾收集器的唯一工作是正确管理它们的生命周期。相信 GC。
  • @Eric:我信任 GC,但我不知道 LinkTo 是否建立了 GC 识别的链接,或者它是否创建了弱引用。这并非完全不可能,想想PropertyObserver 或 Prism 的EventAggregator 两者都不能让订阅者保持活力。 Dataflow 可能会认为只有我明确引用的块才被认为是活动的。

标签: c# tpl-dataflow


【解决方案1】:

您将变量与变量内容混淆了。他们可以有完全不同的生命周期。

一旦控制离开块,局部变量b就不再是GC的根了。被存储在b 中的引用所引用的对象是一个托管对象,只要它可以从根目录访问,GC 就会让它保持活动状态。

现在,请注意,允许 GC 将局部变量视为死控件离开块之前。如果你有:

var a = whatever;
a.Foo(); 
var b = whatever;
// The object referred to by `a` could be collected here. 
b.Foo();
return;

因为例如可能抖动决定b 可以使用与a 相同的本地存储,因为它们的用法不重叠。 只要a 在范围内,a 引用的对象就不会保持活动状态。

如果对象的析构函数具有副作用,您需要将其延迟到块结束时,这可能会导致问题;当您在析构函数中有非托管代码调用时,尤其会发生这种情况。在这种情况下,请使用 keep-alive 使其保持活动状态。

【讨论】:

  • 我想,这看起来与字段不同?对于局部变量,必须使用GC.KeepAlive 之类的变量,当然,以保持它们不被收集。我意识到我的例子选择得不太好,可能会使事情复杂化。
  • @Haukinger:只要包含它们的对象存在,实例字段就会存在。
  • 没错,所以我的问题变成了:每个块都需要一个字段吗?
【解决方案2】:

除了@Eric 对 GC 行为的很好解释之外,我想解决与 TPL-Dataflow 相关的特殊情况。您可以通过简单的测试轻松查看LinkTo 产生的行为。请注意,据我所知,除了指向 a 的链接之外,没有任何东西保留在 b 上。

[TestFixture]
public class BlockTester
{

    private int count;

    [Test]
    public async Task Test()
    {
        var inputBlock = BuildPipeline();
        var max = 1000;
        foreach (var input in Enumerable.Range(1, max))
        {
            inputBlock.Post(input);
        }
        inputBlock.Complete();

        //No reference to block b
        //so we can't await b completion
        //instead we'll just wait a second since
        //the block should finish nearly immediately
        await Task.Delay(TimeSpan.FromSeconds(1));
        Assert.AreEqual(max, count);
    }

    public ITargetBlock<int> BuildPipeline()
    {
        var a = new TransformBlock<int, int>(x => x);
        var b = new ActionBlock<int>(x => count = x);
        a.LinkTo(b, new DataflowLinkOptions() {PropagateCompletion = true});
        return a;
    }
}

【讨论】:

    【解决方案3】:

    是的,链接数据流块足以防止它被垃圾收集。不仅如此,即使没有任何引用,只要有工作要做,块就会一直保持活力,直到它的工作完成。这是一个runnable 示例:

    public static class Program
    {
        static void Main(string[] args)
        {
            StartBlock();
            Thread.Sleep(500);
            for (int i = 5; i > 0; i--)
            {
                Console.WriteLine($"Countdown: {i}");
                Thread.Sleep(1000);
                GC.Collect();
            }
            Console.WriteLine("Shutting down");
        }
    
        static void StartBlock()
        {
            var block = new ActionBlock<int>(item =>
            {
                Console.WriteLine("Processing an item");
                Thread.Sleep(1000);
            });
            for (int i = 0; i < 10; i++) block.Post(i);
        }
    }
    

    输出:

    Processing an item
    Countdown: 5
    Processing an item
    Countdown: 4
    Processing an item
    Countdown: 3
    Processing an item
    Countdown: 2
    Processing an item
    Countdown: 1
    Processing an item
    Shutting down
    Press any key to continue . . .
    

    只要进程中还有一个前台线程还活着,这个块就会继续运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-05-14
      • 2022-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-24
      相关资源
      最近更新 更多