什么时候可以使用(new SomeThing()) { .. } 安全地GC分配但未命名的资源

本文关键字:分配 GC 未命名 资源 可以使 new SomeThing 什么时候 安全 | 更新日期: 2023-09-27 18:37:08

我修复了我的Vsync库(以前称为Isis2)中的一个错误,该错误集中在我和Mono之间对use子句语义的分歧上。 在 Vsync 中,我有锁包装器,可以提高线程优先级以避免优先级反转(其中高优先级线程可能正在等待由低优先级线程持有的锁)。 代码如下所示:

using(new LockAndElevate(myLock)) { ... code protected by myLock ... }

我的理解是,这完全等同于

try { ml = new LockAndElevate(myLock)); ... code ... } finally { ml.Dispose(); }

但实际上,mono 垃圾收集器似乎得出结论,在我的受保护代码中没有对锁定对象的引用(第一个没有使用变量的示例),并且垃圾回收时仍在"受保护"代码块中。 当我将代码更改为以下代码时,此行为将消失:

using(var tmp = new LockAndElevate(myLock)) { ... code ... }

因此,在这里,通过添加一个我从不引用的变量,我可以防止过早的GC。 但是在语义上,他们应该能够进行代码分析并意识到tmp变量没有被引用,删除"var tmp ="部分,此时他们可能会再次过早地错误地收集我的包装锁。 因此,我的修复让我担心,因为未来编译器对 Mono 的改进可能会再次打破这种逻辑。

我只是错了,还是这是一个单声道编译器错误?

什么时候可以使用(new SomeThing()) { .. } 安全地GC分配但未命名的资源

好的,对于那些将来可能会读到这篇文章的人,我将总结人们一直在说的话:

  • @EricLippert觉得我的编码风格不是"using"语句的意图,因此存在风险。 他推荐使用显式变量来保存新资源的(丑陋的)方法,然后使用try-finally 语句,这显然是安全和正确的。
  • @CodeCaster指出Microsoft自己的人一直在使用与我相同的代码样式的情况(像我一样,他们利用 Dispose 来触发清理操作)。 @EricLippert希望他们先和他核实一下。
  • .NET C# 编译器显然不会对资源进行垃圾回收,直到应用程序退出 using 语句,而我观察到 Mono 编译器这样做(但没有一个最小的可重现示例,这对所有相关人员来说都是令人沮丧的)。 但从某种意义上说,这两种行为都符合 C# 规范。 在似乎有人删除的子线程中,有人指出 C# 规范似乎不鼓励我使用的编码风格,尽管Microsoft的内部人员也以相同的方式使用"using"。
  • 可以证明,在这方面,规格不正确。 基本上,一个写得很好的编译器会检测和修剪死代码。 因此,编译器可能会注意到 using 语句中分配的资源实际上并未使用(并且无论您是否声明临时变量,都可能会这样做),清理代码,并最终得出结论,可以安全地删除 using 语句本身。 两个例子——我的,其中有锁定副作用,ASP.NET 的例子,内部块创建一个网页,finally 语句写入输出流,说明了这种推理的缺陷。
  • 因此,use 语句实际上不应该要求使用临时变量命名资源,也不应该要求代码使用引用:由于副作用,资源本身应该被认为是被 using 代码块引用的,并且在代码块终止之前垃圾回收是不安全的, 因为这可能会过早触发 Dispose() 操作。

我希望我总结得正确。 出于我自己的目的,我可能会接受埃里克的建议。 另一方面,这使得一段优雅的代码变得非常丑陋,事实证明,仅仅分配一个临时变量,虽然有点丑陋,但确实解决了问题(目前)。 另外,如上所述,C# 规范本身可能需要在这个特定方面进行改进,因此,Mono 行为很可能会被声明为"不正确"。 所以也许我还不会让我的代码变得非常恶心和恶心!