c# - 终结器在其对象仍在使用时启动

标签 c# .net garbage-collection finalization

总结: 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/

相关文章:

c# - 以 xamarin 形式将 base64 字符串绑定(bind)到 ListView

c# - 如何在 Fluent Assertions 中指定用于检查字典的键和值比较器?

c# - 仅在选定 ListView 记录上显示右键单击选项 - Winforms C#.NET

java - 可能的尝试/捕获和内存管理问题?

c# - 搜索并添加到按字母顺序排列的列表

c# - POST 与 WebRequest 问题

c# - linq中的分组和平均

java - 触发Java垃圾收集的简单程序

javascript - DOM 对象是否在 javascript 中被垃圾收集?

c# - RadComboBox 选中的值为空