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

时间:2018-10-04 10:08:56

标签: c# memory-management garbage-collection exit idisposable

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

当C#.NET 4.5(或更高版本)应用程序关闭时,未正确处置的IDisposables会发生什么?

我知道有些可以终结器来处理非托管资源。

但是,假设我有一个控制台应用程序,带有静态Stream变量。当我关闭控制台应用程序时会丢弃它吗?

HttpClient呢?以及您如何知道在哪些情况下会发生这种情况?

好的,现在提供一些实际的背景信息。我经常将某些IDisposables存储为字段,从而迫使我的类实现IDisposable。最终用户应使用using。但是,如果那没有发生怎么办?

在GC之前是否只是不必要的内存?还是突然有内存泄漏?

4 个答案:

答案 0 :(得分:9)

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

终结器是一种对.NET运行时说的机制,在它可以收集对象之前,它必须执行终结器。当.NET运行时检测到对象符合垃圾收集条件时,就会发生这种情况。通常,如果对象没有终结器,则将在此收集期间将其收集。如果它具有终结器,则将其放置在列表中,即“可访问队列”,并且有一个监视该线程的后台线程。有时,在集合将对象放置到此队列之后,终结器线程将从该队列中处理该对象并调用finalizer方法。

一旦发生这种情况,该对象将再次有资格进行收集,但是它也被标记为已完成,这意味着当垃圾收集器在以后的收集周期中找到该对象时,它不再将其放置在此队列中,而是正常收集。

请注意,在以上文本段落中,没有IDisposable被提及,这是有充分理由的。以上所有都不依赖于IDisposable

现在,实现IDisposable的对象可能具有终结器,也可能没有终结器。一般规则是,如果对象本身拥有非托管资源,则它可能应该拥有,如果没有,则不应该拥有。 (我很犹豫,总是在这里永远不会说,因为似乎总有人能找到一个可以以某种方式有意义但又打破“典型”规则的拐角处)

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

无论如何,我们将带您解决所有具体问题:

  

当C#.NET 4.5(或更高版本)应用程序关闭时,未正确处置的IDisposables会发生什么?

答案:没有。如果它们具有终结器,则终结器线程将尝试拾取它们,因为当程序终止时, all 对象将有资格进行收集。但是,不允许终结器线程“永远”运行以执行此操作,因此它也可能用完时间。另一方面,如果实现IDisposable的对象没有终结器,则将正常地对其进行收集(同样,IDisposable与垃圾回收完全没有关系)。

  

但是,假设我有一个控制台应用程序,带有静态Stream变量。当我关闭控制台应用程序时会丢弃它吗?

答案:否,不会处置Stream本身是基类,因此取决于具体的派生类,它可能有也可能没有终结器。但是,它遵循与上述相同的规则,因此,如果没有终结器,则将仅对其进行收集。例如,MemoryStream没有终结器,而FileStream有终结器。

  

HttpClient呢?以及您如何知道在哪些情况下会发生这种情况,

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

  

好的,现在提供一些实际的背景信息。我经常将某些IDisposables存储为字段,从而迫使我的类实现IDisposable。最终用户应使用using。但是,如果那没有发生怎么办?

答案::如果您忘记/不要在实现IDisposable.Dispose()的对象上调用IDisposable,则一旦对象完成,我在这里所说的有关终结器的一切都会发生。符合收集条件。除此之外,没有什么特别的事情发生。无论对象实现IDisposable是否与垃圾回收过程无关,只有终结器的存在。

  

在GC之前是否只是不必要的内存?还是突然有内存泄漏

答案:根据此简单信息无法确定。这取决于Dispose方法的作用。例如,如果对象已在某处注册了自己,以便在某处对其进行引用,则某些代码停止使用该对象可能实际上未使该对象有资格进行收集。 Dispose方法可能负责注销它,并删除对该方法的最后一个引用。因此,这取决于对象。对象仅实现IDisposable的事实并不会造成内存泄漏。如果删除了对该对象的最后一个引用,则该对象将符合收集条件,并将在以后的收集周期中收集。


备注:

  • 请注意,上面的文字也可能得到了简化。由于没有意义,在应用程序终止时可能没有完成一个完整的收集周期以实际“收集内存”。无论如何,操作系统将释放进程分配的内存。可能仅执行终结处理。 (意思是,我不知道一种或另一种方式在这里完成了哪种优化)

  • 这里最重要的部分是您需要区分 程序执行期间和 程序执行之后

    • 当进程终止时,操作系统将回收分配给它的所有内存,它将关闭所有句柄(这可能会使套接字,文件等保持打开状态),所有线程将终止。简而言之,该程序已从内存中完全删除
    • 该过程可能遗留了一些小窍门,除非该过程事先有所注意,否则这些小消息将不被清除。如上所述,打开的文件已关闭,但它可能尚未完全写入,因此可能以某种方式损坏。
    • 在程序执行过程中,泄漏可能会使程序在分配的内存方面增长,可能由于无法关闭不再需要的句柄而分配了过多的句柄,等等,这对于处理{{1 }}和终结器正确,但是当进程终止时,这不再是问题。

答案 1 :(得分:2)

该过程即将消失。这意味着操作系统将停止提供任何内存来支持该进程的地址空间 1 。无论如何,所有内存都被“回收”。它还将终止进程中的所有线程。因此不会进行进一步处理。无论如何,所有手柄都将关闭。

您需要担心的事情是外部现实。例如。如果您有未处置的Stream,如果它通过网络或文件连接到某个东西,它是否会清空需要的所有内容?

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

如果有人忘记处理您的对象,这就是他们的错误,并且您通过广告宣传应该通过IDisposable界面进行处理,从而完成了应做的事情。


1 进程没有内存。它们具有由OS管理的地址空间。有时可能需要将该地址空间的某些部分存储在内存中。这是操作系统在虚拟内存时代的工作,并且只有每个真正的“临时”将物理内存临时“借给”任何给定的进程。它可以随时带走

答案 2 :(得分:2)

什么都不会自动处置。

实现IDisposable接口的类之所以这样设计,是因为它们要么使用IDisposable字段(例如您的情况),要么使用非托管资源(该规则有例外,但这是不可行的)此答案的范围)。

CLR的任何部分都不会调用Dispose方法。
GC将收集引用,除非另有指示(通过使用GC.SuppressFinalize(),然后将引用移至终结器队列,在此处 它将通过调用它的finalize方法来完成。
当且仅当该类已显式重写finalize方法并在finalize方法中调用Dispose时,该实例才最终被处置。

因此,如果要确保终结器可以处理您的类,则必须override the finalize method in your class。但是,请注意-Implementing the Finalize method correctly is hard!

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

答案 3 :(得分:1)

  

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

是的,是的,不是,但是从技术上来说:不是。

如果IDisposable接口正确实现,则在收集对象时将调用dispose函数。如果未正确实施,则使用非托管资源将导致泄漏。

  

当C#.NET 4.5(或更高版本)应用程序关闭时,未正确处置的IDisposables会发生什么?

释放了处理空间。如果IDisposable隐式地在其他进程空间中保留了内存,并且没有正确处理:您有泄漏。

  

但是,假设我有一个控制台应用程序,带有静态Stream变量。当我关闭控制台应用程序时会丢弃它吗?

尽管隐式,但它可能导致意外结果,例如在您终止进程时。例如:TCP端口可能处于等待状态。

  

HttpClient呢?以及您如何知道在哪些情况下会发生这种情况?

与上述相同。

  

好的,现在提供一些实际的背景信息。我经常将某些IDisposables存储为字段,从而迫使我的类实现IDisposable。最终用户应使用using。但是,如果那没有发生怎么办?

您应该正确实施它,但建议进行处置。例如文件句柄。如果未正确处理该文件,则对该文件的另一个调用可能会失败,因为该文件仍处于打开状态。这可能会在GC中解决,但您不知道何时。

  

在GC之前是否只是不必要的内存?还是突然有内存泄漏?

更多,泄漏,打开的端口,打开的手柄,视频内存消耗等。


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

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