.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块中的逻辑?

.NET 服务器垃圾回收和对象生存期

这个问题有很多事情要做,所以我先解决总体问题。

  1. using 语句中声明的变量不会在块结束之前进行垃圾回收,原因正是您指出的原因 - 保留引用以调用隐式finally块中的Dispose()

  2. 如果你发现自己用 C# 编写终结器,你可能做错了什么。如果你在 C# 中的终结器调用 Stream.Dispose() ,你肯定做错了什么。在 .NET 本身的实现之外,我看到了数百个滥用的终结器,以及实际需要的 1 个终结器。有关详细信息,请参阅 DG 更新:处置、完成和资源管理。

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

    有时,当代码处理Stream时并不明显。让我感到惊讶的一个案例是使用 StreamContent 作为使用 HttpClient 发送 HTTP 请求的一部分。该实现在发送请求后调用Stream.Dispose(),因此我不得不编写一个名为 DelegatingStream 的包装器Stream类,以在从 HttpWebRequest 转换为 HttpClient 期间保留我们库的原始行为。

您可能会看到ObjectDisposedException的一种情况是,如果 getStream() 方法缓存Stream实例并返回该实例以供将来多次调用。如果Request.Dispose()释放了流,或者DoStuff2(Stream)处置了流,那么下次尝试使用该流时,您将获得ObjectDisposedException

根据语言规范,第 3.9 节:"如果对象或其任何部分无法通过任何可能的执行继续访问,除了运行析构函数,则该对象被视为不再使用,并且它有资格销毁。 usingfinally块中引入了Dispose调用(第 8.13 节(,这与运行析构函数不同,并且必须执行,除非在即使finally块也不执行的情况下(规范没有涵盖,通常只发生在整个 AppDomain 无论如何都在前往墓地的路上(。

using块中的对象不符合 GC 的条件。在您的示例中,requestusing 语句之后才有资格销毁,无论它是否在块的其余部分使用。唯一的极端情况(如其他人的评论中所述(是您的Dispose实现不使用其this参数。在这种情况下,Dispose不会阻止对象符合销毁条件 - 但如果发生这种情况,何时可以收集它的问题就没有意义了(因为它应该是 - 垃圾收集,如果正确实现,应该对正确实现的程序没有可观察到的影响(。