总结: C#/.NET 应该被垃圾收集。 C# 有一个析构函数,用于清理资源。当对象 A 在我尝试克隆其变量成员之一的同一行被垃圾收集时会发生什么?显然,在多处理器上,有时垃圾收集器会获胜......
问题
今天,在 C# 培训课上,老师向我们展示了一些仅在多处理器上运行时才包含错误的代码。
我总结一下,有时,编译器或 JIT 在从其调用的方法返回之前调用 C# 类对象的终结器,从而搞砸了。
Visual C++ 2005 文档中给出的完整代码将作为“答案”发布,以避免提出非常非常大的问题,但基本内容如下:
下面的类有一个“哈希”属性,它将返回一个内部数组的克隆副本。在构造函数中,数组的第一项的值为 2。在析构函数中,其值设置为零。
重点是:如果您尝试获取“Example”的“Hash”属性,您将获得数组的一个干净副本,其第一项仍然是 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
}
}
DoWork 每执行 1,000,000 次,显然,垃圾收集器会发挥它的魔力,并尝试回收“ex”,因为它不再在函数的 remaning 代码中被引用,而这一次,它比“哈希”获取方法。因此,我们最终得到的是零字节数组的克隆,而不是正确的字节数组(第 1 项位于 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 上重现该问题,尽管在相同的条件下使用相同的代码(多个相同的可执行文件同时运行、 Release模式等)
最佳答案
这只是您代码中的一个错误:终结器不应访问托管对象。
实现终结器的唯一原因是释放非托管资源。在这种情况下,您应该仔细实现 the standard IDisposable pattern .
使用此模式,您可以实现一个 protected 方法“protected Dispose(bool disposing)”。当从终结器调用此方法时,它会清理非托管资源,但不会尝试清理托管资源。
在您的示例中,您没有任何非托管资源,因此不应实现终结器。
关于c# - 终结器在其对象仍在使用时启动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/134653/