链接是否使链接块保持活动状态?
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 . . .
只要进程中还有一个前台线程还活着,块就会继续运行。
当使用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 . . .
只要进程中还有一个前台线程还活着,块就会继续运行。