3

例如,如果我忘记写语句,垃圾收集器会自动释放与某个实例关联的非托管资源(实际上是什么) ?IDisposableusing

显然,我不知道什么时候会发生这种情况IDisposable,但是GC当我不关心这些资源并且我可以接受它们最终会被处置时,可以吗?

4

6 回答 6

18

例如,如果我忘记编写 using 语句,垃圾收集器是否会自动释放与某些 IDisposable 实例关联的非托管资源(实际上是什么)?

通常,但不一定。一次性资源的作者需要做正确的事。

显然,我不知道什么时候会发生这种情况,但是当我不关心这些资源并且我可以接受它们最终会被处理时,将 IDisposable 留给 GC 可以吗?

你的问题是以虚假为前提的。垃圾收集器永远不会调用 Dispose。 相反,垃圾收集器调用析构函数(或“终结器”,如果您愿意)。它是可能会或可能不会为健忘用户调用 Dispose 的析构函数。

你的问题也表明态度不好。可能不关心资源是否延迟释放,但另一个程序肯定会关心!如果您的客户正在运行两个程序,它们都试图访问相同的非托管资源,一个由您编写,一个由其他人编写,该怎么办? 做一个好公民;完成后立即释放您的稀缺资源,以便其他程序可以使用它们。这就是“使用”的真正目的——通过确保快速回收稀缺资源来保持礼貌。

Dispose 模式的正确实现将确保析构函数在用户忘记调用 Dispose 时清理非托管资源,并确保析构函数在他们记得时不会清理资源。

如果您是为拥有非托管资源的类编写 Dispose 实现的人,那么您有责任在用户未正确调用 Dispose 的情况下正确获取析构函数代码。 编写此代码的正确方法非常有据可查;根据规律。有关一些有用的链接,请参阅 Jon 的评论。

另请注意,如果您正在编写这样的类,那么您还需要使其在用户无法调用 Dispose 的情况下工作。例如,假设构造函数分配了两个非托管资源,并且在第一个和第二个分配之间,在构造函数之外抛出并捕获了一个异常。这样用户就无法调用 Dispose,因为新对象引用的分配发生在构造函数成功运行之后,并且构造函数从未成功完成运行。第一个非托管资源是如何释放的?只有析构函数才能释放它。面对这种情况,析构函数必须是健壮的;被破坏的对象可能永远不会完全构造,因此您不能依赖构造函数的任何不变量。

于 2011-04-29T18:48:43.113 回答
3

是的,它们最终会被清理干净。只有在需要确定性资源清理时才需要使用using(heh..),例如用于 GDI、Sql 和其他基于系统句柄的代码,或者用于可能占用大量内存的对象,例如DataTable.

编辑:这是假设您Dispose正确实现了模式并调用了析构函数Dispose

于 2011-04-29T18:44:53.813 回答
2

如果对象通过在终结器中包含对 Dispose 方法的条件调用正确实现了 IDisposable 接口,则 GC 将在收集期间触发对象的处置(通过终结器)。

有些事情会导致这种情况不会发生,但如果遵循标准模式,那么它应该在大多数情况下都可以工作。

如果对象在终结器中不包含处置代码,则所有赌注都关闭。在太阳超新星爆发之前,未管理的资源可能会一直未被处理。

于 2011-04-29T18:46:34.047 回答
1

不,不一定。这取决于您如何获得非托管资源。

如果你有一个非托管资源的直接句柄IntPtr(可能是一个),那么你应该有一个终结器或使用SafeHandle... 否则你肯定泄漏非托管资源。

如果您的类型只是引用了 a 之类的东西FileStream,那么您应该期望类型将具有终结器。

现在很少需要从您的类型中直接访问非托管资源,如果您使用的是框架类型,它们确实应该适当地清理(但不确定)。不过,这是可能的。

于 2011-04-29T18:47:58.260 回答
0

系统库中的任何IDisposable内容都会在完成时自行处理(或者至少会采取与处理相同的步骤)。

无法保证第三方的东西——例如,如果您使用某个 C 库的接口为您提供句柄,则这些句柄可能不会被清理,直到库被卸载(这往往只发生在应用程序退出时)。但是很常见的是忘记使用Disposeusing(或者甚至不知道或不关心某种类型是一次性的),任何没有考虑到它的 .net 库都写得很糟糕,IMO。

于 2011-04-29T18:52:46.483 回答
0

创建对象时,如果它覆盖 Object.Finalize,系统会将其添加到已注册终结器的对象列表中。该列表中的对象以及它们直接或间接引用的对象将没有资格被消除,除非或直到对它们调用 GC.SuppressFinalize,或者以其他方式将它们从列表中删除。如果系统注意到这样的对象将有资格被淘汰,但它存在于可终结对象列表中,则系统将从具有已注册终结器的对象列表中删除 if 并将其添加到应具有其终结器的对象列表中。例行公事尽快调用。每当后一个列表非空时,系统将在那里执行对象的 Finalize 例程。

Note that the Garbage collector doesn't actually Dispose anything--all it does is call the Finalize routine of objects which have registered for finalization. Although many types of objects have Finalize routines which can ensure cleanup if they are wrongfully abandoned, finalization can be fraught with peril. Objects will seldom be finalized at exactly the right time; they'll often not be finalized until long after they've been abandoned, and in some cases they may be finalized while their resources are still in use. It's thus wise to avoid relying upon finalizers. There are some occasions when it's necessary, but they're almost always icky.

BTW, note that if objects of a certain class directly subscribe to events from objects with a longer useful life, neither the former objects (event subscribers), nor any objects to which they hold direct or indirect references, will become eligible for elimination or finalization until the latter objects do. While one might argue that all classes should clean up after themselves if they are abandoned, ensuring that an event subscriber can find out that's been abandoned requires the creation and use of wrapper objects. This will add considerable complexity and impair performance in the case where the class is used properly. Trying to clean up events with a finalizer when not using wrapper objects would be pointless; the class with dangling events wouldn't be eligible for collection until the event publisher became eligible, and once that happened it wouldn't matter whether the former class unsubscribed or not.

于 2011-04-29T23:11:28.683 回答