当前位置:  开发笔记 > 编程语言 > 正文

Finalizer在其对象仍在使用时启动

如何解决《Finalizer在其对象仍在使用时启动》经验,为你挑选了1个好方法。

简介: 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上重现问题,尽管在相同条件下使用相同的代码(同时运行多个相同的可执行文件,发布模式等)



1> Joe..:

它只是代码中的一个错误:终结器不应该访问托管对象.

实现终结器的唯一原因是释放非托管资源.在这种情况下,您应该仔细实现标准IDisposable模式.

使用此模式,您可以实现受保护的方法"protected Dispose(bool disposing)".从终结器调用此方法时,它会清除非托管资源,但不会尝试清理托管资源.

在您的示例中,您没有任何非托管资源,因此不应该实现终结器.

推荐阅读
mobiledu2402851323
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有