我对此有所了解,但我忘记了,在那里我看到了一个例子。所以它看起来像这样
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方法返回之前执行!如果发生这种情况,我们可能会 在使用时或在使用之前关闭手柄。
但我无法重现这种行为
答案 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方法之前调用。
答案 2 :(得分:0)
问题是因为你在Foo中使用线程。你告诉代码等待1秒,但你没有告诉它等待第二个代码在执行其他所有操作之前。因此,原始线程在Foo完成之前执行析构函数。
写Foo的更好方法是这样的:
public void Foo()
{
var mre = new ManualResetEvent(false);
mre.WaitOne(1000);
Console.WriteLine("Foo");
}
使用ManualResetEvent将强制代码完全暂停,直到在这种情况下,命中超时。之后代码将继续。