C# 在析构函数运行后调用方法
我已经读过了,但是我忘了,我在哪里看到了一个例子。看起来是这样的C# 在析构函数运行后调用方法,c#,.net,optimization,compilation,clr,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; Consol
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表现:萨沙·戈尔施泰因、迪玛·祖尔巴列夫、伊多·弗拉托 另一个问题与终结的异步性质有关 它发生在专用线程中。终结器可能会尝试 获取由应用程序代码持有的锁,然后 应用程序可能正在通过调用 GC.WaitForPendingFinalizers()。解决这个问题的唯一办法是 在超时情况下获取锁,如果无法获取,则正常失败 获得。还有一种情况是由垃圾收集器的 渴望尽快恢复记忆。考虑一下 下面的代码表示一个文件类的幼稚实现 使用关闭文件句柄的终结器:
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方法返回之前执行!如果发生这种情况,我们可能会
在使用手柄时或使用前关闭手柄
但是我不能复制这种行为不管怎样,我找到了复制它的方法,我应该更仔细地阅读:): 所以在这种情况下,将在Foo方法之前调用析构函数。
问题是因为您在Foo中使用了线程。您告诉代码等待1秒,但不告诉它在执行其他所有操作之前等待第二秒。因此,原始线程在Foo完成之前执行析构函数 编写Foo的更好方法如下:
public void Foo()
{
var mre = new ManualResetEvent(false);
mre.WaitOne(1000);
Console.WriteLine("Foo");
}
使用ManualResetEvent将强制代码完全暂停,直到达到超时。之后,代码将继续
public void Foo()
{
Thread.Sleep(1000);
Console.WriteLine("Foo");
}
不使用类的任何实例成员的方法应该声明为静态的。它有几个优点,对代码的读者非常有帮助。它明确指出,方法不会改变对象状态
另一个好处是,您现在可以理解为什么在对象完成后看到方法运行没有差异。GC只是没有任何理由让它保持活动状态,当Foo()开始执行时,没有对对象的引用。因此,毫无困难地收集并最终确定
您将在中找到更多关于jitter如何向垃圾收集器报告对象引用的背景信息。Code在
deleted
之前仍然打印Foo
,因为Main
方法仍在执行,所以SomeClass
实例从未超出范围,因此不符合GC收集的条件。也许您在中编译调试?您应该将自己的回复标记为答案。我认为为了完整性,你需要提到的是,如果你有问题,使用“解决”了问题。不是RY,只是一篇关于一些鲜为人知的优化的帖子。我只能在两天内标出我的答案。@RohitVats对不起,我从未见过这样的文章。重要的是要记住,优化是一个实现细节,在编译器版本之间可能会发生变化。通过C#book(第510页,第四部分:核心设施,第21章:托管堆和垃圾收集)和各种互联网文章(如:我可以使用字段,并且由于Clr标准,输出应该是相同的),Clr中已经知道并描述了这种精确的优化。简单的WriteLine只是一个例子,它当然不一样了。由于“CLR标准”,方法永远无法访问最终确定对象的字段。很明显,这将是灾难性的。但仍有人写道,确定局部变量何时被视为活动根的规则规定,在调用Read之后,客户端持有的局部变量不再相关。因此,该对象被认为有资格进行垃圾收集,并且它的终结器可能会在Read方法返回之前执行!如果发生这种情况,我们可能会在使用句柄时关闭它,或者在使用句柄之前关闭它。
那么这是错误的吗?不,这不是错误的。这正是您所看到的,当Foo()正在执行时,对象是gc-ed的。newsomeclass()
引用不再相关。你不把它存储在局部变量中,但这没关系。我试图用一个字段来复制它,但没有成功。字段现在是Stream
,方法调用Stream.Read
,所以现在对象在Foo方法完成后被删除,不管它工作多长时间。
public void Foo()
{
Thread.Sleep(1000);
Console.WriteLine("Foo");
}