析构函数运行后调用方法

时间:2013-12-01 07:10:17

标签: c# .net optimization compilation clr

我对此有所了解,但我忘记了,在那里我看到了一个例子。所以它看起来像这样

public class Program
{
    private static void Main()
    {
        new SomeClass(10).Foo();
    }
}

public class SomeClass
{
    public int I;

    public SomeClass(int input)
    {
        I = input;
        Console.WriteLine("I = {0}", I);
    }

    ~SomeClass()
    {
        Console.WriteLine("deleted");
    }

    public void Foo()
    {
        Thread.Sleep(2000);
        Console.WriteLine("Foo");
    }
}

所以输出应该是:

I = 10
deleted
Foo

为什么呢?由于优化。它看到该方法不使用任何字段,因此它可以在调用方法之前销毁对象。那为什么不这样做呢?

如果我找到了,我会发一个例子。


所以我找到了消息来源。 Pro .NET性能:Sasha Goldshtein,Dima Zurbalev,Ido Flatow

  

另一个问题与终结的异步性质有关   这发生在专用线程中。终结者可能会尝试   获取应用程序代码所持有的锁,以及   应用程序可能正在等待完成调用完成   GC.WaitForPendingFinalizers()。解决此问题的唯一方法是   如果不能,则在超时时获取锁定并优雅地失败   收购。另一种情况是由垃圾收集器引起的   渴望尽快收回记忆。考虑一下   下面的代码代表了一个简单的File类实现   使用关闭文件句柄的终结器:

class File3
{
    Handle handle;
    public File3(string filename)
    {
        handle = new Handle(filename);
    }
    public byte[] Read(int bytes)
    {
        return Util.InternalRead(handle, bytes);
    }
    ~File3()
    {
        handle.Close();
    }
}

class Program
{
    static void Main()
    {
        File3 file = new File3("File.txt");
        byte[] data = file.Read(100);
        Console.WriteLine(Encoding.ASCII.GetString(data));
    }
}
     

这段无辜的代码可能会以非常恶劣的方式破解。阅读   方法可能需要很长时间才能完成,而且它只使用句柄   包含在对象中,而不是对象本身。规则   确定何时将局部变量视为活动根指令   客户端持有的局部变量在之后不再相关   已发送对Read的调用。因此,对象是   被认为有资格进行垃圾收集及其终结者可能   在Read方法返回之前执行!如果发生这种情况,我们可能会   在使用时或在使用之前关闭手柄。

但我无法重现这种行为

3 个答案:

答案 0 :(得分:2)

public void Foo()
{
    Thread.Sleep(1000);
    Console.WriteLine("Foo");
}

不使用类的任何实例成员的方法应声明为 static 。这有几个优点,它对于代码的读者非常有帮助。它毫不含糊地声明方法不会改变对象状态。

另一方面有一个很大的优势,你现在可以理解为什么在对象最终确定后看到方法运行没有差异。 GC没有任何理由让保持活动状态,当Foo()开始执行时,没有任何引用留给对象。所以收集和最终确定没有任何麻烦。

您将在this answer中找到有关抖动如何报告对垃圾收集器的引用的更多背景信息。

答案 1 :(得分:0)

无论如何,我找到了重现它的方法,我应该读得更细心:):

public class Program
{
    private static void Main()
    {
        new Thread(() =>
                   {
                       Thread.Sleep(100);
                       GC.Collect();
                   }).Start();
        new SomeClass(10).Foo();
    }
}

public class SomeClass
{
    public int I;

    public SomeClass(int input)
    {
        I = input;
        Console.WriteLine("I = {0}", I);
    }

    ~SomeClass()
    {
        Console.WriteLine("deleted");
    }

    public void Foo()
    {
        Thread.Sleep(1000);
        Console.WriteLine("Foo");
    }
}

所以在这种情况下,析构函数将在Foo方法之前调用。 enter image description here

答案 2 :(得分:0)

问题是因为你在Foo中使用线程。你告诉代码等待1秒,但你没有告诉它等待第二个代码在执行其他所有操作之前。因此,原始线程在Foo完成之前执行析构函数。

写Foo的更好方法是这样的:

public void Foo()
{
  var mre = new ManualResetEvent(false);
    mre.WaitOne(1000);

  Console.WriteLine("Foo");
}

使用ManualResetEvent将强制代码完全暂停,直到在这种情况下,命中超时。之后代码将继续。