为什么CLR不处理清理代码?

3
我刚开始接触.NET框架。今天,我学习了关于IDisposable接口和dispose()方法的一些内容。我学到了以下几点:
dispose()应该包含与对象相关的清理代码(例如关闭任何由对象占用的资源-文件或数据库连接等)。
我还被告知,如果我们不在dispose()方法中执行清理操作,则可以在析构函数中执行,但这不能确保立即执行,而且我们只能依赖垃圾回收器执行。
如果我们根本没有提供任何清理代码,GC将强制终止我们的对象所持有的所有资源的所有连接。因此,我们应该自己处理清理代码。
但我很好奇为什么CLR不能自行处理呢?它负责内存管理,它负责垃圾收集。因此,它应该非常清楚哪个对象保持哪些资源以及何时该对象消亡。因此,它也应该能够释放这些资源,对吗?
我向一些人询问了这个问题。我得到的答案是因为我们需要优雅地关闭,而GC则强制关闭。这真的是原因吗?

GC收集托管对象,而IDisposable处理非托管对象,我说得对吗? - Alvin Wong
因为垃圾回收是非确定性的。你真的想让数据库连接比必要的时间更长时间保持打开状态吗? - vcsjones
如果我们根本没有提供任何清理代码,GC将强制终止我们的对象所持有的所有资源的连接。这是不正确的。GC并不知道这些,所以处理它们是你的责任。 - Brian Rasmussen
嗯,GC将会断开这些连接;而这些端点将会一直保持打开状态,直到操作系统在你的程序运行后进行清理。 - Joey
8个回答

3
在.NET中,垃圾收集器所知道的不仅仅是托管代码。实际上,有大量未经处理的代码:所有文件句柄、数据库连接、网络套接字等都是纯粹的未经处理的Win32代码。你甚至无法相信,在几乎每个BCL函数中,你从漂亮的C#应用程序中调用的函数都会触发大量未经处理的C++函数(也可能是VB6),并深深地嵌入到操作系统的内部。所有这些函数都分配未经处理的内存、句柄等。托管世界不知道那里发生了什么。
例如,每次打开文件(FileStream)时,您基本上是在调用(当然是在幕后)CreateFile非托管Win32函数。该函数直接从文件系统分配非托管文件句柄。.NET和GC严格无法跟踪此非托管代码及其所有操作。这就是为什么这些类实现IDisposable接口的原因。因此,您始终可以在using语句中包装它们的实例,并确保始终调用Dispose方法,即使在发生异常的情况下也是如此,而且尽早这样做。Dispose方法将负责调用另一个非托管函数来清理所创建的混乱。
因此,您可以考虑IDisposable接口的方式如下:
当我们拥有一个完全由托管语言编写的操作系统时(例如微软研究中的Midori),我们可能不再需要IDisposable,因为GC将能够完全替代它,它将了解此系统内发生的一切。

谢谢您的回答。解释得非常清楚。 :-) - Kazekage Gaara

2
IDisposableDispose() 的目的是让您清理非托管内存。这是.NET没有分配的内存,来自外部来源,因此GC无法知道它。所以它不能自动地为您清理它。实际上,这正是托管和非托管内存之间的区别;-)
一般来说,您应该实现Dispose()来清除类所使用的任何非托管资源,并实现终结器来调用Dispose()。然而,终结器只是一个保护措施。如果调用方忘记正确地处理您的类,则它将确保这些资源最终得到清理。

谢谢你的回答,以及上面的评论。解释得非常清楚。但我会接受另一个答案作为正确的答案。再次感谢。 :-) - Kazekage Gaara

1

IDisposable 接口提供了一种清理非托管资源的方法,CLR 仅为您管理托管资源。

换句话说,CLR 只知道如何清理它管理的东西。如果打开与系统的其他部分的连接(例如打开文件、数据库连接等),那就是您的责任,并且您需要告诉CLR 如何为您清除这些内容。


我的评论比你早3秒钟 :P - Alvin Wong

1

IDisposable 接口只是一种约定,允许您确定性地处理托管和非托管资源。它本身不会替换垃圾回收或涉及垃圾回收器本身的任何操作。

对于非托管资源来说,这更加明显,因为除非这些资源被处理(在终结器中或通过确定性处理),否则它们将作为内存泄漏保留,直到进程结束。对于托管内存,如果您没有确定性地处理这些项,它们将被 GC 不确定性地收集(假设最终有资格进行收集),因为它们是托管的(这也是为什么处理模式不包括在终结器路线中的托管项的原因)。

IDisposable 本身并不执行任何操作,它只是一个公认的接口(并且在使用可消耗资源、非托管内存、外部项等处理项时支持使用 using 关键字的代码中)。

CLR 不可能知道外部项何时完成。这完全取决于您的应用程序流程。如果您也不知道何时处理对象,那么终结器语法很有用。如果在自定义类上实现终结器,则垃圾回收过程将在最终收集之前运行此终结器。这是您最后一次整理自己的机会。


1

它只能处理.NET对象的内存管理。任何需要使用非托管资源的代码(例如与C++库交互)都不在垃圾回收器的控制范围之内。所有这些代码都需要按照传统方式告知何时释放其资源。


1

对于.Net框架(和GC)来说,没有办法知道如何释放非托管资源。它所能做的就是销毁您的托管代码对资源的引用。最好的方法是在连接到数据库服务器时实际调用.Close()(从而告诉它连接应该返回到可用连接池中),而不仅仅是销毁引用,并让其在一定时间后自动超时。

因此,尽可能使用IDisposable接口引用非托管资源!


1

IDisposable 用于在不希望 GC 处理特定工件时使用。最常见的例子是连接或文件句柄。您不希望等待 GC 运行以释放文件或关闭到数据库的连接,因为您不知道何时会发生。

大多数人将 IDisposable 与非托管资源相关联,这基本上是准确的,但他们忘记了终结器是处理这些资源的正确 .NET 方式。如果确定性处理对您的程序很重要,则 IDisposable 提供了一种确定性处理的方式。


0

我们使用Dispose来释放非托管资源,例如文件访问或数据库连接,因为垃圾回收器没有关于这些非托管资源的信息。

你也可以使用Finalize,但它不是高效的,因为你需要将你的资源保存在终结结构中,而垃圾回收器会通过这个终结结构在Dispose周期结束时进行处理,这不是高效的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接