C# 应用程序退出是否自动处理托管资源?

Does C# app exit automatically dispose managed resources?

我完全知道 using 语句是处理 IDisposable 的方法。请不要在评论中重复此建议。

当 C# .NET 4.5(或更高版本)应用程序关闭时,未正确处理的 IDisposable 会怎样?

我知道有些有用于处理非托管资源的终结器。

但是假设我有一个带有静态 Stream 变量的控制台应用程序。当我关闭控制台应用程序时它是否被处理?

HttpClient 怎么样?你怎么知道它在哪些情况下有效,在哪些情况下无效?

好的,现在是一些实际的背景信息。我经常将某些 IDisposable 存储为字段,迫使我的 class 实现 IDisposable。最终用户应该使用using。但如果那没有发生呢?

在GC之前仅仅是不必要的内存吗?还是突然内存泄漏?

Does C# app exit automatically dispose managed resources?

好吧,是也不是,但从技术上讲:不是。

如果IDisposable接口实现正确,回收对象时会调用dispose函数。如果没有正确实现,使用非托管资源就会发生泄漏。

When a C# .NET 4.5 (or higher) application closes, what happens to the IDisposables which were not properly disposed?

进程 space 已释放。如果 IDisposable 在其他进程 space 中隐式保留内存,并且未正确处置:您有泄漏。

But let's say I have a console app, with a static Stream variable. Is it disposed when I close the console app?

虽然是隐式的,但它可能会导致意想不到的结果,例如当您终止进程时。例如:TCP 端口可能处于等待状态。

What about a HttpClient? And how do you know in which situations it does and in which is does not?

同上

Alright, now some actual background info. I often store certain IDisposables as fields, forcing my class to implement IDisposable. The end user should use using. But what if that does not happen?

你应该正确实施它,但建议处置。例如文件句柄。如果处理不当,对该文件的另一次调用可能会失败,因为该文件仍处于打开状态。这可能会在 GC 上解决,但你不知道什么时候。

Is it merely unnecessary memory until GC? Or do you suddenly have a memory leak?

更多,泄漏,开放端口,开放句柄,视频内存消耗等


有关 IDisposable 的正确实施,请参阅:Proper use of the IDisposable interface

如果您阅读它,您可以想象并非所有第 3 方库都正确实现了它。

进程即将消失。这意味着 OS 将停止提供任何内存来支持该进程的地址 space1。无论如何,所有内存都是"reclaimed"。它还将终止进程中的所有线程。所以不会发生进一步的处理。反正所有句柄都会关闭。

你要担心的是外部现实。例如。如果你有一个未处理的 Stream,如果它通过网络或文件附加到某个东西,它是否刷新了它需要的所有东西?

是的,如果您自己存储 Disposable,您应该实施 IDisposable。但是,如果您没有任何 unmanaged 资源,请不要实施终结器。在终结器运行时,您不应该从该代码访问任何其他 managed 对象,因此处理它们为时已晚。

如果有人忘记处理您的对象,那是 他们的 错误,您已经尽了最大的努力,通过广告宣传您应该通过 IDisposable界面。


1进程没有内存。他们的地址 space 由 OS 管理。有时,该地址 space 的某些部分可能需要在内存中。那是 OS 在虚拟内存时代的工作,它只是每个 "loans" 物理内存临时给任何给定的进程。随时可以再拿走

永远不会自动处理任何内容。

实现 IDisposable 接口的

类 是这样设计的,因为它们要么使用 IDisposable 字段(就像你的情况),要么使用非托管资源(这有例外规则,但这超出了这个答案的范围)。

CLR 中没有调用 Dispose 方法的部分。
GC 将收集引用,除非另有指示(通过使用 GC.SuppressFinalize(),然后将引用移动到终结器队列,其中 它将通过调用它的 finalize 方法来完成。
如果且仅当 class 已显式覆盖 finalize 方法并在 finalize 方法中调用 Dispose 时,实例最终将被释放。

所以,如果你想确保你的 classes 被终结器处理掉,你必须 override the finalize method in your class. However, Beware - Implementing the Finalize method correctly is hard!

话虽这么说,当您实现 IDisposable 接口时,您是在告诉正在使用此 class 的任何人应该将其丢弃。他们是否真的处置它不再是你的责任——这是他们的责任。因此,如果确实存在内存泄漏(并且很有可能会发生内存泄漏)- 假设您 class implemented the IDisposable interface correctly,那不是您的问题。

区分实现 IDisposable 的对象和带有终结器的对象很重要。在大多数情况下(可能最好是所有),具有终结器的对象也实现 IDisposable 但实际上它们是两个不同的东西,最常一起使用。

终结器是一种机制,告诉 .NET 运行时在它可以收集对象之前,它必须执行终结器。当 .NET 运行时检测到对象符合垃圾回收条件时,就会发生这种情况。通常,如果对象没有终结器,它将在本次收集期间被收集。如果它有一个终结器,它会被放在一个列表中,"freachable queue",并且有一个后台线程监视这个线程。有时在集合将对象放入此队列后,终结器线程将处理此队列中的对象并调用终结器方法。

一旦发生这种情况,该对象再次符合收集条件,但它也被标记为已完成,这意味着当垃圾收集器在未来的收集周期中找到该对象时,它不再将其放在这个排队但正常收集。

请注意,在上述文字段落中,IDisposable一次未提及,这是有充分理由的。 None 以上完全依赖 IDisposable .

现在,实现 IDisposable 的对象可能有也可能没有终结器。一般规则是,如果对象本身拥有非托管资源,则它可能应该拥有,如果不拥有,则可能不应该拥有。 (我在这里犹豫要不要说总是和从不,因为似乎总是有人能够找到一个角落箱,在这种情况下它以某种方式有意义但违反了 "typically" 规则)

A TL;DR 上面的总结可能是终结器是一种在收集对象时获得(半)保证清理对象的方法,但确切地说当这种情况发生时,并不直接在程序员的控制之下,而实现 IDisposable 是一种直接从代码控制这种清理的方法。

无论如何,我们已经完成了所有工作,让我们来解决您的具体问题:

When a C# .NET 4.5 (or higher) application closes, what happens to the IDisposables which were not properly disposed?

答:没有。如果它们有终结器,终结器线程将尝试拾取它们,因为当程序终止时,所有 对象都符合收集条件。终结器线程不允许 运行 "forever" 执行此操作,但是,因此它也可能 运行 超时。另一方面,如果实现 IDisposable 的对象没有终结器,它将被简单地正常收集(同样,IDisposable 与垃圾收集完全无关)。

But let's say I have a console app, with a static Stream variable. Is it disposed when I close the console app?

答:不会,不会处置Stream 本身是一个基础 class,因此取决于具体派生的 class 它可能有也可能没有终结器。然而,它遵循与上面相同的规则,所以如果它没有终结器,它将被简单地收集。例如,MemoryStream does not have a finalizer, whereas FileStream 可以。

What about a HttpClient? And how do you know in which situations it does and in which is does not

答案: reference source for HttpClient 似乎表明 HttpClient 没有终结器。因此,它将被简单地收集起来。

Alright, now some actual background info. I often store certain IDisposables as fields, forcing my class to implement IDisposable. The end user should use using. But what if that does not happen?

答案:如果你forget/don不对实现IDisposable的对象调用IDisposable.Dispose(),我在这里所说的关于终结器的一切都将一旦对象符合收集条件,仍然会发生。除此之外,不会有什么特别的事情发生。对象是否实现 IDisposable 与垃圾回收过程无关,只有终结器的存在才具有影响。

Is it merely unnecessary memory until GC? Or do you suddenly have a memory leak

答:从这个简单的信息中无法确定。这取决于 Dispose 方法会做什么。例如,如果对象已经在某处注册了自己,以便在某处存在对它的引用,那么某些停止使用该对象的代码实际上可能不会使该对象符合收集条件。 Dispose 方法可能负责注销它,删除对它的最后引用。所以这个要看对象。仅对象实现 IDisposable 的事实不会造成内存泄漏。如果删除对该对象的最后一个引用,该对象将符合收集条件,并将在未来的收集周期中被收集。


备注:

  • 请注意,上面的文字也可能是简化的。实际上 "collect memory" 的完整收集周期可能不会在应用程序终止时完成,因为没有意义。无论如何,操作系统都会在进程终止时释放进程分配的内存。可能只执行终结。 (意思是,我不知道这里做了什么样的优化)

  • 这里更重要的部分是你需要区分内存(或其他)泄漏程序执行期间和之后 程序执行

    • 当进程终止时,操作系统将回收分配给它的所有内存,它会关闭所有句柄(可能保持套接字、文件等打开),所有线程将被终止。总之,程序完全从内存中删除
    • 虽然该进程可能留下了自己的花絮,但这些花絮 不会 清理,除非该进程事先注意这样做。如上所述,打开的文件已关闭,但它可能尚未完全写入,因此可能以某种方式损坏。
    • 在程序执行期间,泄漏可能会使程序在分配的内存方面增长,它可能会分配太多的句柄,因为它无法关闭不再需要的句柄,等等。这在处理方面很重要IDisposable 和终结器正确,但是当进程终止时,这不再是问题。