链接是否使链接块保持活动状态?

Does linking keep the linked block alive?

当使用System.Threading.Tasks.Dataflow时,如果我link阻止a阻止b,link会保持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;
}

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

一旦控制离开块,局部变量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 使其保持活动状态。

除了@Eric 对 GC 行为的精彩解释之外,我还想解决与 TPL-Dataflow 相关的特殊情况。您可以通过简单测试轻松地看到 LinkTo 产生的行为。请注意,据我所知,除了它的 link 到 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;
    }
}

是的,链接数据流块足以防止它被垃圾收集。不仅如此,即使没有任何引用,只要有工作要做,块就会保持活动状态,直到它的工作完成。这是一个 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 . . .

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