简介: C#/ .NET应该是垃圾回收.C#有一个析构函数,用于清理资源.当一个对象A被垃圾收集在同一行我试图克隆其变量成员之一时会发生什么?显然,在多处理器上,有时,垃圾收集器赢了......
问题
今天,在关于C#的培训课程中,老师向我们展示了一些仅在多处理器上运行时才包含错误的代码.
我将总结一下,有时候,编译器或JIT通过在从被调用方法返回之前调用C#类对象的终结器来搞砸.
在Visual C++ 2005文档中给出的完整代码将作为"答案"发布,以避免提出非常大的问题,但基本要点如下:
以下类具有"Hash"属性,该属性将返回内部数组的克隆副本.在构造中,数组的第一项值为2.在析构函数中,其值设置为零.
关键是:如果你试图得到"示例"的"哈希"属性,你将获得一个干净的数组副本,其第一个项目仍然是2,因为正在使用该对象(因此,不是垃圾收集/定稿):
public class Example { private int nValue; public int N { get { return nValue; } } // The Hash property is slower because it clones an array. When // KeepAlive is not used, the finalizer sometimes runs before // the Hash property value is read. private byte[] hashValue; public byte[] Hash { get { return (byte[])hashValue.Clone(); } } public Example() { nValue = 2; hashValue = new byte[20]; hashValue[0] = 2; } ~Example() { nValue = 0; if (hashValue != null) { Array.Clear(hashValue, 0, hashValue.Length); } } }
但没有什么是这么简单的......使用这个类的代码在一个线程中运行,当然,对于测试,应用程序是多线程的:
public static void Main(string[] args) { Thread t = new Thread(new ThreadStart(ThreadProc)); t.Start(); t.Join(); } private static void ThreadProc() { // running is a boolean which is always true until // the user press ENTER while (running) DoWork(); }
DoWork静态方法是问题发生的代码:
private static void DoWork() { Example ex = new Example(); byte[] res = ex.Hash; // [1] // If the finalizer runs before the call to the Hash // property completes, the hashValue array might be // cleared before the property value is read. The // following test detects that. if (res[0] != 2) { // Oops... The finalizer of ex was launched before // the Hash method/property completed } }
每隔1,000,000个DoWork提供一次,显然,垃圾收集器会发挥其魔力,并试图回收"ex",因为它不再在函数的重新编写代码中引用,而这次,它比"哈希"更快得到方法.所以我们最终得到的是一个零ed字节数组的克隆,而不是正确的一个(第一项为2).
我的猜测是代码的内联,它基本上取代了DoWork函数中标记为[1]的行:
// Supposed inlined processing byte[] res2 = ex.Hash2; // note that after this line, "ex" could be garbage collected, // but not res2 byte[] res = (byte[])res2.Clone();
如果我们认为Hash2是一个简单的访问器,编码如下:
// Hash2 code: public byte[] Hash2 { get { return (byte[])hashValue; } }
所以,问题是:这应该在C#/ .NET中以这种方式工作,还是可以认为这是JIT编译器的错误?
编辑有关解释,请参阅Chris Brumme和Chris Lyons的博客.
http://blogs.msdn.com/cbrumme/archive/2003/04/19/51365.aspx
http://blogs.msdn.com/clyon/archive/2004/09/21/232445.aspx
每个人的回答都很有趣,但我不能选择一个比另一个好.所以我给了你一个+1 ...
抱歉
:-)
编辑2我无法在Linux/Ubuntu/Mono上重现问题,尽管在相同条件下使用相同的代码(同时运行多个相同的可执行文件,发布模式等)
它只是代码中的一个错误:终结器不应该访问托管对象.
实现终结器的唯一原因是释放非托管资源.在这种情况下,您应该仔细实现标准IDisposable模式.
使用此模式,您可以实现受保护的方法"protected Dispose(bool disposing)".从终结器调用此方法时,它会清除非托管资源,但不会尝试清理托管资源.
在您的示例中,您没有任何非托管资源,因此不应该实现终结器.