问题 这里是否需要GC.KeepAlive,或者我可以依赖本地和参数来保持对象存活吗?


我有一堆采用WPF的方法 WriteableBitmap 并从中读取 BackBuffer 直接使用不安全的代码。

我是否应该使用它并不完全清楚 GC.KeepAlive 每当我做这样的事情:

int MyMethod(WriteableBitmap bmp)
{
    return DoUnsafeWork(bmp.BackBuffer);
}

一方面,仍有提及 bmp 上 MyMethod堆栈。另一方面,它似乎依赖于实现细节 - 这可以编译为尾调用,例如,不保留引用 bmp 此时此刻 DoUnsafeWork 输入。

同样,想象下面的假设代码:

int MyMethod()
{
    WriteableBitmap bmp1 = getABitmap();
    var ptr = bmp.BackBuffer;
    WriteableBitmap bmp2 = getABitmap();
    return DoUnsafeWork(ptr, bmp2);
}

在理论上,参考 bmp1 在方法返回之前,它仍保留在堆栈中,但同样,它似乎使用了实现细节。当然编译器可以自由合并 bmp1 和 bmp2 因为它们永远不会同时存在,即使编译器从来没有这样做,JITter仍然可以,并且可能会(例如通过将它们存储在同一个寄存器中,第一个,然后另一个)。

所以,一般来说:我应该依赖locals / arguments作为对象的有效引用,还是应该总是使用 GC.KeepAlive 保证正确吗?

这显然是令人费解的,显然, FxCop认为GC.KeepAlive总是很糟糕


3246
2018-01-03 14:29


起源

相关阅读: 我什么时候需要使用 GC.KeepAlive? 和 什么时候对象可用于垃圾回收? 来自Raymond Chen的博客。 - Daniel Pryden
@DanielPryden优秀链接;在询问之前,我搜索了第一个但不是第二个。除了我已经问过的内容,它解释了这一点 this 不会使当前对象保持活动状态。 - Roman Starkov


答案:


我应该依赖locals / arguments作为对象的有效引用吗?

不,你的分析是正确的;抖动完全在其权限内告诉垃圾收集器本地的内容在托管代码不再使用的那一刻就已经死了。

我是否应该始终使用GC.KeepAlive来保证正确性?

是。这就是它的用途。


12
2018-01-03 14:47





如果在获取后台缓冲区之前调用了WriteableBitmap.Lock,则应该固定WriteableBitmap并且不会移动底层内存。至少这是我对WriteableBitmapAPI的解释。

我已经广泛使用了WriteableBitmap,特别是codeplex上的开源WriteableBitmapEx库(披露,我已经为这个库贡献了一次,但它不是我的项目)。这使用Lock / Access Backbuffer(重复)/ unlock / Invalidate的模式进行绘制。即使后备缓冲区IntPtr存储在结构中并传递给应用程序,我也从未遇到过这种模式的问题。

最好的祝福,


0
2018-01-03 14:49



我知道Lock / Unlock会出现,而且我可能会顽皮地在阅读时没有锁定它(文档只解释了为什么你应该在写作时锁定)。但是,这与修复指针无关; BackBuffer根据MSDN,保证是固定的。 - Roman Starkov
嗯,不知道,谢谢!我会对自己的代码进行一些测试,因为锁定/解锁是一项昂贵的操作:)关于你的问题,我看到GC做了一些奇怪的事情,比如收集仍在范围内但不再使用的东西,特别是在发布模式下。使用代码示例在Release或Debug中获得不同的结果吗? - Dr. ABT
问题是理论上的;我还没有看到任何关于此类代码的实际不当行为。事实上,如果碰巧总是在当前的编译器/ JIT上工作,我不会感到惊讶。 - Roman Starkov
我有。我有一个单元测试,一旦收集了一个引用,因为在光标下面它不再在范围内(尽管仍然在相同的范围块)。构建服务器总是编译发布模式,而开发人员在调试中测试,因此它在服务器上偶尔会失败(我的意思是非常间歇性),但在本地盒子上从未失败过!需要在此特定情况下使用GC.KeepAlive(ref) - Dr. ABT