【问题标题】:Dispose question处理问题
【发布时间】:2011-08-30 19:27:56
【问题描述】:

我有许多具有实现 IDisposable 的私有成员变量的类(计时器、画笔等)。我是否需要做任何事情来确保 .NET Framework 正确清理这些变量?

我遇到的文献是指“托管资源”与“非托管资源”。这些术语让我感到困惑,因为您可以拥有一个使用非托管资源实现功能的托管类。这被认为是“非托管资源”还是“托管资源”?

我的理解是,如果您不对实现 IDisposable 的对象调用 Dispose(),那么在应用程序退出之前不会释放资源。长时间运行程序时,这种情况可能会导致 OutOfMemory 异常。

如何确保我的代码正确处理资源管理?这对这些对象很重要,因为它们是自定义控件,并且可能存在大量消耗 IDisposable 资源的绘图。我尽可能使用 C# using 语句,但有时我需要使实现 IDisposable 的对象成为成员变量,而 using 语句在这方面无济于事。

【问题讨论】:

标签: c# .net dispose


【解决方案1】:

Three simple rules.

托管资源是实现IDisposable 的任何东西。非托管资源类似于您通过 p/Invoke 获得的 HANDLE。像SafeHandle(或从SafeHandle 派生的类)这样的类拥有非托管资源,但它本身被视为托管资源。所以任何拥有非托管资源的类本身就是一个托管资源。

由于您有一个拥有托管资源的类,请遵循规则 2:实现 IDisposable(但不是终结器)。

IDisposable 允许提前清理。如果你不调用它,资源无论如何都会被清理(它们不会在进程退出之前徘徊);它们稍后会被清理,您无法选择何时清理它们。

【讨论】:

  • 一些实现 IDisposable 的类如果被放弃将被正确清理。然而,并非所有人都会。如果一个交互器在一个锁内调用“yield return”,并且该迭代器返回的 IEnumerator 被放弃而没有被 Dispose,则锁将永远不会被释放。
  • 就我个人而言,我不会将“任何实现 IDisposable 的东西”称为托管资源 - 特别是,我 将内存称为托管资源,并且它由垃圾收集器管理 - 而我将直接(“本机”句柄)或间接(通过另一种 CLR 类型)称为从显式清理中受益的任何内容。
  • @Jon:我明白你关于术语的观点,但我希望区分原始本机资源和 CLR 包装资源。 Microsoft sometimes uses 任何 IDisposable 的术语“托管资源”和 other times uses(IMO 尴尬的)“使用本机资源的托管对象”指的是一次性类型。
  • @Stephen:我认为区分这两者是合理的,但我通常会讨论可以直接访问非托管资源的类型和可以间接访问的类型。
  • @Trevor:如果您从未在派生类上调用 Dispose,这是预期的(并且是正确的)。在这种情况下,您的派生 Dispose 正在从终结器中调用。
【解决方案2】:

是的 - 如果你的类“包含”IDisposable,那么该类应该几乎当然也实现IDisposable

“托管”资源基本上是内存。 “非托管”资源可以是文件句柄、网络连接、图形对象的句柄等。在大多数情况下,直接访问本机句柄的类型都有终结器,因此资源将在某个时候被释放,但最好还是明确地释放它——在某些情况下(例如HttpWebResponse),可用的此类资源数量可能有限(在这种情况下,连接池中的连接到单个主机)并且您最终可能会超时等待释放“死”资源。

在可能的情况下,最好首先拥有这样的类成员 - 将它们作为局部变量等的方法参数,这样你就可以使用它们然后关闭它们而不用绑定生命周期资源到对象的生命周期。但是,在某些情况下这是不合适的 - 在这种情况下,您应该实现 IDisposable

【讨论】:

  • 我认为重要的是要注意“非托管”资源实际上可以是任何地方,任何地方;它们可能完全存在于托管世界中(例如,一个从静态数组中保存的池中分配项目以最小化 GC 压力的类),或者可能存在于当前机器以外的其他地方(例如,远程机器可能已授予独占访问数据库记录)。基本上 GC 不知道如何清理的任何东西。
【解决方案3】:

非常全面的 IDisposable 指南是 here

传递性地处理你的类型中定义的任何一次性字段 来自您的 Dispose 方法。

您应该在任何字段上调用 ​​Dispose() 您的对象控制其生命周期。例如,考虑一个案例 您的对象拥有一个私有 TextReader 字段。在你的类型中 Dispose,您应该调用 TextReader 对象的 Dispose,它将 依次处理其一次性字段(Stream 和 Encoding,例如 例)等等。如果在 Dispose(bool disposing) 内部实现 方法,这应该只发生在处理参数是 true — 期间不允许接触其他托管对象 定稿。此外,如果您的对象不拥有给定的 一次性物品,它不应该像其他人一样尝试处置它 代码仍然可以依赖它处于活动状态。这两种情况都可能导致 微妙的检测错误。

当你的类型被解封并且 包含明确需要或可以释放的资源,例如 例如原始句柄或其他非托管资源。

这种模式 为开发人员提供了一种标准化的方法来确定性地 销毁或释放对象拥有的资源。它还有助于子类 正确释放基类资源。

“非托管资源”通常是指您的代码直接引用本机句柄(文件句柄、连接、套接字等)的情况。在这种情况下,您还必须实现终结器或使用SafeHandle。大多数情况下,您通过 TextReader 等 .NET 类间接引用本机句柄。在这种情况下,您可以简单地使用“使用”,或者,如果您正在编写库,则可传递地实现 IDisposable。

【讨论】:

    【解决方案4】:

    如果您的类具有实现 IDisposable 的成员变量,那么您的类也应该实现它。你清理你拥有的东西。

    【讨论】:

      【解决方案5】:

      您的理解中有一些好的信息和一些错误的信息。

      总而言之,您需要 Dispose() 任何实现 IDisposable 的东西。

      因为这些是你的类的私有成员变量,并且如果这些应该在该类的实例的生命周期内可用,你的类也应该实现 IDisposableDispose()这些类型在它自己的Dispose() 方法中。

      如果这些私有成员变量的生命周期有限(即仅在一种方法中),只需将它们包装在 using 块中。

      【讨论】:

        【解决方案6】:

        1) 您可以使用 Memory Profiler Tool,网络上有很多,我所知道的最好的是 Reg Gate 的 ANTS Profiler。

        2) 我的经验法则是必须始终取消订阅事件,如果一次性对象(Streams 等)是成员变量并且持有它们的对象被销毁,则它们将被自动处置。 例如,如果您在方法中创建本地一次性对象,则必须将其处置,或者只是将其放在 using 语句中并忘记它;)

        【讨论】:

          【解决方案7】:

          我认为将托管资源描述为实现 IDisposable 并需要清理的类类型对象是最有帮助的,但如果它在未正确 Dispose 的情况下被放弃,则可以执行此类清理(通常使用 Finalize)。非托管资源通常是指需要清理的实体,如果它在没有首先被 Dispose 的情况下被放弃,则根本不会发生这种情况。重要的是要注意,虽然术语“托管资源”基本上专门指类类型的对象(通常会覆盖 Finalize,但在某些情况下可能是 WeakReference 对象的目标),但非托管资源可能不仅是 任何东西,它们也可能在任何地方,包括在另一台计算机上。

          我建议不要使用“资源”这个词,而是从“责任”的角度来思考更有帮助。打开文件或套接字连接会产生关闭它的责任。获取锁会产生释放它的责任。向远程系统发送“授予我对该记录的独占访问权限”消息会产生向其发送“我已完成此记录”消息的责任。如果一个对象的清理职责即使被废弃也能被执行,那么它就是一个“托管资源”。否则,它就是“非托管资源”。

          仅仅将事物分类为“托管资源”或“非托管资源”不足以决定如何清理它们。一些非托管职责可以方便地由类类型包装器处理,该包装器可以在不正确放弃的情况下执行任何必要的清理。此类包装器通常应包含执行清理职责所需的最少信息。其他职责不能很好地自动处理。确保调用 Dispose 通常比尝试处理可能发生的所有事情(如果没有调用)要好。

          【讨论】:

            猜你喜欢
            • 2015-07-24
            • 2020-05-24
            • 2016-06-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-04-07
            相关资源
            最近更新 更多