2014-12-05 30 views
8

我自己和一位同事在.NET中垃圾收集对象时有不同的看法。看看下面的代码:.NET服务器垃圾收集和对象生命周期

Stream stream=getStream(); 
using(var request=new Request(stream)) 
{ 
    Stream copy=request.Stream; 

    // From here on can "request" be garbage collected? 

    DoStuff1(); 
    DoStuff2(copy); 
} 

我的同事声称,在发布版本中使用服务器垃圾收集器,它是有效的request对象是调用request.Stream后垃圾回收运行。他断言这只会发生在服务器垃圾收集器上,而不会发生在工作站垃圾收集器上。

之所以这样,是因为Request类有一个终结器正在关闭给予请求的Stream。因此,当DoStuff2去使用流时,它得到了“对象处置”异常。由于终结器只能由垃圾收集器运行,所以我的同事说垃圾收集必须发生在finally块的末尾之前,但是在最后一次使用后request

但是,我相信自从上述代码只是速记是这样的:

Stream stream=getStream(); 
Request request=null; 

try 
{ 
    Stream copy=request.Stream; 

    // From here on can "request" be garbage collected? 

    DoStuff1(); 
    DoStuff2(copy); 
} 
finally 
{ 
    if(request!=null) 
     request.Dispose(); 
} 

然后request不能被垃圾调用request.Stream后收集它仍然是来自finally块到达。

另外,如果垃圾回收器有可能收集对象,则finally块可能会显示未定义的行为,因为Dispose将在GC对象上调用,这是没有意义的。同样,也无法优化掉finally块作为异常可以在try/using块任何垃圾回收已经发生之前,这将需要finally块执行中被抛出。

忽略终结器中关闭流的问题是否有可能垃圾收集器在finally块的末尾之前收集对象,实际上优化finally块中的逻辑?

+2

你是对的,直到finally运行,请求才能被GCed。 – 2014-12-05 12:55:17

+1

请注意,如果request.Dispose没有使用实例字段,请求*可以在对其调用Dispose之前收集。这是一种罕见的情况。 – usr 2014-12-05 12:58:06

+0

建议:不要使用终结器。然后,这个问题就变得没有意义了。您无法在工作中检测到​​GC(除非通过非常不寻常的方式,例如终结器或调试API)。为什么Request.Finalize需要关闭流?流本身有一个终结器。 – usr 2014-12-05 12:59:11

回答

10

这个问题有很多,所以我会先解决总体问题。

  1. using声明中声明的变量不会被垃圾块结束之前收集的正是你所指示的原因 - 一个参考,以便调用Dispose()在隐finally块举行。

  2. 如果您发现自己在C#中编写终结器,那么您可能是在做错某些事情。如果你在C#中的终结器调用Stream.Dispose(),你肯定是做错了什么。在.NET本身的实现之外,我看到了数百个被滥用的终结器,以及实际需要的exactly 1 finalizer。有关更多信息,请参阅DG Update: Dispose, Finalization, and Resource Management

  3. ObjectDisposedException与最终确定无关。当代码在对象上调用Dispose()(不是finalize)时,通常会发生此异常,然后稍后调用该对象执行某些操作。

    有时代码处置Stream时并不明显。一个让我感到意外的例子是使用StreamContent作为使用HttpClient发送HTTP请求的一部分。实现在发送请求后调用Stream.Dispose(),所以我必须编写一个包装StreamDelegatingStream,以便在从HttpWebRequestHttpClient转换期间保留我们库的原始行为。

一种情况,你可以看到一个ObjectDisposedException是,如果getStream()方法将缓存Stream实例,并返回其多个未来的呼叫。如果Request.Dispose()处置该流,或者如果DoStuff2(Stream)处置该流,则下次您尝试使用该流时,将获得ObjectDisposedException

+0

绝对为真。 Object Disposal是框架实现的一种模式实现,它允许轻松地等待对象释放资源并且不直接绑定到GC。唯一的限制是Disposed对象通常释放资源,并且此资源可用于删除。在Stream中,您需要检查每种情况下的实现以查看发生了什么。 – Miguel 2014-12-12 04:33:26

7

根据language specification的第3.9节“如果对象或其任何部分无法通过任何可能的继续执行来访问,除了运行析构函数之外,该对象被认为不再使用,并且它有资格销毁。“ using介绍了一个finallyDispose调用(第8.13节),这是其他东西比运行析构函数,并且除了在情况必须时执行甚至finally块不执行(该规范并没有涵盖,而且通常只发生无论如何,当你的整个AppDomain到达坟墓场时)。

using块一个目的是不符合GC。在你的榜样,request不符合销毁条件,直到using语句之后,不论它是在块的其余部分使用。唯一的角落案例(如其他人的评论中所述)是当您的Dispose实施不使用其参数this时。在这种情况下,Dispose不会阻止对象是符合销毁条件 - 但如果这种情况发生,当可以收集它的问题是没有实际意义(因为它应该是 - 垃圾收集,正确的实施,应该没有对正确实施的计划有可观察的影响)。

+1

我认为using语句创建了一个弱引用,但是在语言规范中没有看到这种效果。检查变量是否可以GC'ed的方法是调用GC.ForceGC *两次*。第一次调用将导致Dispose方法被调用。第二次调用将导致释放对象内存 – 2014-12-10 21:30:59

+0

@ KC-NH:没有'GC.ForceGC'方法。通过实现一个终结器,设置一个外部标志并调用'GC.Collect()',然后调用'GC.WaitForPendingFinalizers()',可以人为地检测一个对象是否有资格被破坏。显然这不是你想要在生产代码中做的事情,所以它只有教学价值。如果你在'using'中用一个变量来尝试这个,你会发现直到'using'完成才能收集它。在我的测试中,即使'Dispose()根本不访问任何成员,情况也是如此。 – 2014-12-10 22:31:15

+0

对不起。我的意思是GC.Collect。我会通过让Dispose()记录它被调用来测试它。如果你有一个终结器,记录它也被调用。 – 2014-12-10 22:34:04