而仍在使用的对象终结推出对象

2023-09-03 15:19:39 作者:一个人的精彩

摘要: C#/。NET被认为是垃圾收集。 C#有一个析构函数,用来清洁资源。当对象A被垃圾回收在同一行我尝试克隆它的可变成员之一发生什么事?显然,在多处理器上,有时,垃圾收集胜...

Summary: C#/.NET is supposed to be garbage collected. C# has a destructor, used to clean resources. What happen when an object A is garbage collected the same line I try to clone one of its variable members? Apparently, on multiprocessors, sometimes, the garbage collector wins...

问题

今天,在C#的一堂训练课上,老师给我们看了其中载有关于多处理器上运行,只有当错误一些code。

Today, on a training session on C#, the teacher showed us some code which contained a bug only when run on multiprocessors.

我会总结说,有时,编译或JIT砸了从其调用的方法返回前调用C#类对象的终结。

I'll summarize to say that sometimes, the compiler or the JIT screws up by calling the finalizer of a C# class object before returning from its called method.

满code,在Visual C ++ 2005的文档给定,将公布为答案,以避免一个非常非常大的问题,但重要的是如下:

The full code, given in Visual C++ 2005 documentation, will be posted as an "answer" to avoid making a very very large questions, but the essential are below:

下面的类有一个哈希属性,它会返回一个内部数组的克隆副本。在是建筑,数组的第一项具有2.在析一个值,其值被设置为零。

The following class has a "Hash" property which will return a cloned copy of an internal array. At is construction, the first item of the array has a value of 2. In the destructor, its value is set to zero.

的一点是:如果你想获得的榜样的哈希属性,你会得到数组,其第一个项目依然是200的一个全新副本,因为对象正在被使用(并因此,不是被垃圾收集/定稿):

The point is: If you try to get the "Hash" property of "Example", you'll get a clean copy of the array, whose first item is still 2, as the object is being used (and as such, not being garbage collected/finalized):

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);
        }
    }
}

但没有什么是这么简单... 使用这个类的code为wokring一个线程内,当然,对于测试,该应用程序是严重的多线程:

But nothing is so simple... The code using this class is wokring inside a thread, and of course, for the test, the app is heavily multithreaded:

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的静态方法是code其中发生问题:

The DoWork static method is the code where the problem happens:

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
    }
}

在每100万名excutions DoWork的,显然,垃圾回收器它的魔力,并试图收回恩,因为它不再在功能remaning code引用,而这一次,它是比哈希get方法更快。所以,我们到底是不是有正确的(含有2第1项)一零版字节数组的克隆。

Once every 1,000,000 excutions of DoWork, apparently, the Garbage Collector does its magic, and tries to reclaim "ex", as it is not anymore referenced in the remaning code of the function, and this time, it is faster than the "Hash" get method. So what we have in the end is a clone of a zero-ed byte array, instead of having the right one (with the 1st item at 2).

我的猜测是,有的code,基本上取代标志着行内联[1]通过类似的DoWork的功能:

My guess is that there is inlining of the code, which essentially replaces the line marked [1] in the DoWork function by something like:

    // 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是一个简单的访问codeD这样的:

If we supposed Hash2 is a simple accessor coded like:

// Hash2 code:
public byte[] Hash2 { get { return (byte[])hashValue; } }

所以,问题是:?这应该工作的方式在C#/。NET,或可这被认为是JIT任编译器的一个bug

请参阅克里斯Brumme的和克里斯里昂的博客作出解释。

See Chris Brumme's and Chris Lyons' blogs for an explanation.

http://blogs.msdn.com/cbrumme/存档/ 2003/04/19 / 51365.aspx http://blogs.msdn.com/clyon/archive/2004 /09/21/232445.aspx

每个人的回答很有意思,但我不能选择一个比另一个更好。所以我给大家+1 ...

Everyone's answer was interesting, but I couldn't choose one better than the other. So I gave you all a +1...

抱歉

: - )

我无法重现在Linux / Ubuntu的/单声道的问题,尽管使用相同的code在相同条件下(同时在多个相同的可执行文件运行后,释放模式,等等。)

I was unable to reproduce the problem on Linux/Ubuntu/Mono, despite using the same code on the same conditions (multiple same executable running simultaneously, release mode, etc.)

推荐答案

它只是在你的code的错误:终结不应该访问管理对象

It's simply a bug in your code: finalizers should not be accessing managed objects.

要实现一个终结的唯一原因是释放非托管资源。在这种情况下,你应该认真落实标准IDisposable模式。

The only reason to implement a finalizer is to release unmanaged resources. And in this case, you should carefully implement the standard IDisposable pattern.

通过这种模式,要实现保护法保护的Dispose(BOOL处置)。当这个方法是从所谓的终结,它清理非托管资源,但不尝试清理托管资源。

With this pattern, you implement a protected method "protected Dispose(bool disposing)". When this method is called from the finalizer, it cleans up unmanaged resources, but does not attempt to clean up managed resources.

在你的榜样,你没有任何非托管资源,因此不应该实施一个终结。

In your example, you don't have any unmanaged resources, so should not be implementing a finalizer.