18

我在一个项目中工作,其中有大量对象被几个类实例化,这些类在应用程序的整个生命周期内都保留在内存中。不时抛出 OutOfMemoryExceptions 会导致大量内存泄漏。似乎在实例化对象之前超出范围之后,它们没有被垃圾收集。

我已将问题隔离为主要与附加到永不分离的长寿命对象的事件处理程序有关,从而导致长寿命对象仍然具有对超出范围对象的引用,然后将永远不会垃圾收集。

我的同事提出的解决方案如下:在所有类上实现 IDisposable,全面和在 Dispose 方法中,使您的对象中的所有引用为空,并从您附加到的所有事件中分离。

我相信这是一个非常糟糕的主意。首先是因为它“矫枉过正”,因为这个问题主要可以通过修复一些问题区域来解决,其次是因为 IDisposable 的目的是释放您的对象控制的任何非托管资源,而不是因为您不信任垃圾收集器。到目前为止,我的论点被置若罔闻。我怎样才能让他们相信这是徒劳的?

4

11 回答 11

16

巧合的是,我刚刚在其他地方发表了这条评论:

对未正确保留的对象的引用仍然是资源泄漏。这就是为什么 GC 程序仍然可能存在泄漏的原因,通常是由于观察者模式 - 观察者在列表中,而不是可观察者,并且永远不会被删除。最终, remove每个 都需要adda ,就像每个delete都需要a 一样new。完全相同的编程错误,导致完全相同的问题。“资源”实际上只是一对必须使用相应参数调用相同次数的函数,而“资源泄漏”是当你未能做到这一点时发生的事情。

你说:

的目的IDisposable是释放您的对象控制的任何非托管资源

现在,事件上的+=and-=运算符实际上是一对函数,您必须使用相应的参数(事件/处理程序对是相应的参数)调用相同次数的函数。

因此,它们构成了一种资源。由于 GC 不会为您处理(或“管理”)它们,因此将它们视为另一种非托管资源可能会有所帮助。正如 Jon Skeet 在评论中指出的那样,非托管通常具有特定的含义,但在IDisposable我认为将其扩展为包括在“建立”之后必须“拆除”的任何资源的情况下是有帮助的.

所以事件分离是处理IDisposable.

当然,您需要在Dispose某个地方调用,并且不需要在每个对象上都实现它(只是那些具有需要管理的事件关系的对象)。

另外,请记住,如果一对对象通过事件连接,并且您“使它们漂流”,则通过丢失所有其他对象中对它们的所有引用,它们不会使彼此保持活力。GC 不使用引用计数。一旦一个对象(或对象岛)无法访问,它就可以被收集。

您只需要担心作为事件处理程序登记的对象,这些对象会在长时间存在的对象上发生事件。例如,一个静态事件,例如AppDomain.UnhandledException,或应用程序主窗口上的事件。

于 2009-08-07T08:30:07.763 回答
11

将他们指向Joe Duffy 关于 IDisposable/finalizers 的帖子——结合了许多聪明人的智慧。

我目前发现很难看到那里有一个声明说“当你不需要它时不要实施它” - 但除此之外,向他们展示正确实施它所涉及的复杂性可能有助于劝阻他们它...

不幸的是,如果人们不听,他们就不会听。试着让他们解释为什么他们认为他们需要IDisposable。他们认为垃圾收集器不起作用吗?向他们展示它有效。如果你能说服他们(对于大多数类型)它没有用,那么他们肯定会停止为自己添加工作......

正如布赖恩所说,实现IDisposable本身并不能帮助解决事件问题——它实际上需要被某些东西调用。在这种情况下,终结器也不会为您提供帮助。他们确实需要明确地做一些事情来删除事件处理程序。

于 2009-08-07T08:23:35.997 回答
9

Dispose()在所有类型中实施并不能解决您的问题。请记住,Dispose()不会自动调用,它与回收托管内存无关。为了使您的Dispose()方法产生任何效果,您需要在所有相关位置调用它 - 显式或通过using

换句话说,只是IDisposable到处实现不会神奇地解决您的问题,因为Dispose()除非您还更改代码中每种类型的用法,否则不会调用方法。

但是,我建议IDisposable在所有类型上实现,因为它没有任何意义。该接口用于指示所讨论的类型使用了一些资源,而垃圾收集器未处理这些资源。

事件引用由垃圾收集器处理。如果您的发布者的寿命明显长于订阅者,您只需要取消订阅即可。一旦发布者死亡,订阅者也将死亡。

于 2009-08-07T08:25:40.500 回答
5

我曾经帮助一位同事解决了发生 OutOfMemoryException 错误的类似问题(由事件导致对象引用悬而未决)。我做的第一件事是通过 FXCop 运行代码,其中突出显示的 Dispose 没有在 IDisposable 类上调用。

修改要处理的代码解决了这个问题。也许您应该推荐使用 FXCop?

也许在源存储库中找到存在问题的代码,在其上运行 FXCop - 看看它是否突出显示了问题(如果它是由 .NET Framework 类引起的,它可能会突出显示)并用它来说服你的同事。

于 2009-08-07T08:27:31.560 回答
2

这很难实现,但我发现让人们做你想做的事情的最好方法是让他们认为这是他们的想法。你比我更了解它们,但是像“如果有办法找出为什么这些物体挂这么长时间就好了”和“我希望我能更多地了解关于物体的事件”这样的短语可能是一个起点。

于 2009-08-07T08:33:39.470 回答
2

询问他们是否愿意在使用自行车后被迫关闭电机,即使没有电机。

或者,如果他们想在离开工作场所之前被迫按下椅子、桌子、咖啡杯和其他物品上的“关闭”按钮,即使没有任何东西可以关闭。

实现 IDisposable 会强制用户在不再使用对象时明确告知对象。如果这个对象不需要清理任何东西,那只是不必要的复杂性。

顺便说一句,恕我直言,通过实现 IDisposable 取消注册事件是清理事件的合适方法。有一些东西可以“关闭”。

于 2009-08-07T08:34:07.130 回答
1

IDisposable 仅用于释放非托管资源(SafeHandles 等),并且 Dispose 方法通过类层次结构传播。它并不意味着试图绕过不良的非托管编程实践。

于 2009-08-07T08:28:01.980 回答
1

创建解决另一个 OutOfMemoryException 的责任。当有人解决属于其他人的错误时,就会对自己的代码负责。在我们项目的早期阶段,我们被推荐为“傻瓜标志” - 对致命错误负责一天得到这个标志。

于 2009-08-07T08:30:00.883 回答
1

提出一个比他们的解决方案优越得多的解决方案;-)

例如,一个简单的替代方法是WeakReference在长寿命对象中使用 s 来保存对事件处理程序的引用。这将要求事件处理程序在需要时在其他地方被引用,但是一旦它们超出范围,它们就会被垃圾收集,并且可以从长期存在的对象中删除弱引用。

于 2009-08-07T08:44:58.747 回答
0

通常,如果您使用事件注册处理程序,那么“取消注册”只是每个对象在被销毁时应该进行的基本清理。如果你在语言中没有析构函数,那么你必须定义一个方法来调用来告诉对象它正在消失。该方法应该清理事件处理程序。

这不是 IDisposable 的用途吗?如果已经存在,为什么还需要另一个解决方案?为什么那些笨蛋一开始没有正确地实现他们的对象呢?;)

于 2009-08-07T13:43:29.463 回答
0

一个对象需要实现 IDisposable 如果它需要确保它自身之外的东西可能比它更长寿在被遗弃之前被清理干净。某些对象的属性与其他对象“连接”,因此更改这些属性将更改其他对象;有时需要将这些属性设置为 null。在 vb.net 中,“WithEvents”字段实际上是附加和分离事件处理程序的属性,因此 vb.net 中的 WithEvents 字段应设置为 Nothing。请注意,对于一个对象来说,纯粹为了清空自己的字段而实现 IDisposable 通常是没有用的。在某些情况下它可能会有所帮助(例如,如果一个已经存在很长时间的对象持有对最近创建的对象的引用,清除该引用可能会比它更快地收集最近创建的对象否则)但肯定没有必要。

What is necessary is to ensure that objects which need to clean up other objects implement IDisposable and ensure those other objects get cleaned up. I am peeved at Microsoft for encouraging people to write code which abandons event handlers. While it is true that one can get away with abandoning event handlers in cases where the event publishers don't outlive the subscribers, and such cases commonly occur with interlinked GUI elements, I really don't see any reason event shouldn't always be cleaned up as a matter of course.

于 2011-02-11T16:18:26.750 回答