.NET 服务器垃圾回收和对象生存期
本文关键字:对象 生存期 服务器 NET | 更新日期: 2023-09-27 18:33:17
我和一位同事对何时可以在 .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.Stream
后对request
对象进行垃圾回收是有效的。他断言,这只发生在服务器垃圾回收器上,而永远不会发生在工作站垃圾回收器上。
原因是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();
}
然后,在仍然可以从finally
块访问request.Stream
调用request
后,无法对其进行垃圾回收。
此外,如果垃圾回收器可以收集对象,则finally
块可能会表现出未定义的行为,因为Dispose
将在 GC'd 对象上调用,这没有意义。同样,不可能优化finally
块,因为在发生任何垃圾收集之前,try
/using
块中可能会引发异常,这将需要执行finally
块。
忽略在终结器中关闭流的问题,垃圾回收器是否有可能在finally
块结束之前收集对象,并实际上优化finally
块中的逻辑?
这个问题有很多事情要做,所以我先解决总体问题。
-
在
using
语句中声明的变量不会在块结束之前进行垃圾回收,原因正是您指出的原因 - 保留引用以调用隐式finally
块中的Dispose()
。 -
如果你发现自己用 C# 编写终结器,你可能做错了什么。如果你在 C# 中的终结器调用
Stream.Dispose()
,你肯定做错了什么。在 .NET 本身的实现之外,我看到了数百个滥用的终结器,以及实际需要的 1 个终结器。有关详细信息,请参阅 DG 更新:处置、完成和资源管理。 -
ObjectDisposedException
与最终确定无关。当代码调用对象(未完成(Dispose()
,然后稍后调用以对该对象执行某些操作时,通常会发生此异常。有时,当代码处理
Stream
时并不明显。让我感到惊讶的一个案例是使用StreamContent
作为使用HttpClient
发送 HTTP 请求的一部分。该实现在发送请求后调用Stream.Dispose()
,因此我不得不编写一个名为DelegatingStream
的包装器Stream
类,以在从HttpWebRequest
转换为HttpClient
期间保留我们库的原始行为。
您可能会看到ObjectDisposedException
的一种情况是,如果 getStream()
方法缓存Stream
实例并返回该实例以供将来多次调用。如果Request.Dispose()
释放了流,或者DoStuff2(Stream)
处置了流,那么下次尝试使用该流时,您将获得ObjectDisposedException
。
根据语言规范,第 3.9 节:"如果对象或其任何部分无法通过任何可能的执行继续访问,除了运行析构函数,则该对象被视为不再使用,并且它有资格销毁。 using
在finally
块中引入了Dispose
调用(第 8.13 节(,这与运行析构函数不同,并且必须执行,除非在即使finally
块也不执行的情况下(规范没有涵盖,通常只发生在整个 AppDomain 无论如何都在前往墓地的路上(。
using
块中的对象不符合 GC 的条件。在您的示例中,request
在 using
语句之后才有资格销毁,无论它是否在块的其余部分使用。唯一的极端情况(如其他人的评论中所述(是您的Dispose
实现不使用其this
参数。在这种情况下,Dispose
不会阻止对象符合销毁条件 - 但如果发生这种情况,何时可以收集它的问题就没有意义了(因为它应该是 - 垃圾收集,如果正确实现,应该对正确实现的程序没有可观察到的影响(。