【问题标题】:Do I need to call Dispose() on managed objects?我需要在托管对象上调用 Dispose() 吗?
【发布时间】:2010-03-30 20:57:07
【问题描述】:

我不敢相信我仍然对此感到困惑,但无论如何,让我们最终确定:

我有一个重写 OnPaint 的类来做一些绘图。为了加快速度,我在构造函数中预先创建了钢笔、画笔等,这样 OnPaint 就不需要继续创建和处理它们了。

现在,我确保始终处置此类对象,但我觉得我不需要这样做,因为尽管它们实现了 IDisposable,但它们是托管对象。

这对吗?


感谢大家的回答,问题肯定已经解决了。
我很高兴我一直保持警惕,始终使用“使用”,这样我就不需要检查所有的代码。我只是想明确一点,我不是一个毫无意义的用户。

顺便说一句,最近我确实遇到了一个奇怪的情况,我不得不替换一个 using 块并手动调用 dispose!我会挖掘出来并创建一个新问题。

【问题讨论】:

    标签: .net idisposable


    【解决方案1】:

    这是正确的。您需要处置实现IDisposable 的对象。这就是他们实现IDisposable 的原因 - 以指定他们包装(直接或间接)非托管资源的事实。

    在这种情况下,非托管资源是一个 GDI 句柄,如果您在实际处理完它们时未能释放它们,您将泄漏这些句柄。现在这些特定的对象有finalizers,它们会在GC启动时导致资源被释放,但是你无法知道什么时候会发生。可能是 10 秒后,可能是 10 天后;如果您的应用程序没有产生足够的内存压力来导致 GC 启动并在这些画笔/钢笔/字体/等上运行终结器,那么您最终可能会在 GC 意识到发生了什么之前让操作系统缺乏 GDI 资源。

    此外,您无法保证每个非托管包装器实际上都实现了终结器。 .NET Framework 本身在实现 IDisposable 的类与 correct pattern 实现它的意义上是相当一致的,但是某些 other 类完全有可能有一个不包含的错误实现一个终结器,因此不会正确清理,除非在其上显式调用 Dispose。一般来说,IDisposable 的目的是你不应该知道或关心具体的实现细节;相反,如果它是一次性的,那么你就丢弃它,句号。

    故事的寓意:始终处置IDisposable 对象。如果您的类“拥有”IDisposable 的对象,那么它应该自己实现 IDisposable

    【讨论】:

    • 我可能将终结器的使用称为“理想”模式,但在许多情况下 IDisposable 对象无法有效地实现终结器,例如他们使用来自未知对象的线程静态变量或事件订阅。终结器在与其他代码不同的线程上下文中运行,这将很难访问与在另一个线程上创建的废弃 IDisposable 关联的线程静态变量。虽然可以将事件设计为由终结器解除挂钩,但大多数事件在这种情况下无法安全解除挂钩。
    • @supercat:这是一个非常危险的设计,将非托管资源存储在线程本地存储中。确实,终结器可能会在不同的线程中运行,但你如何保证Dispose 将从导致资源初始化的同一线程中调用?我可以想到几种情况并非如此。老实说,不要将 ThreadStatic 用于此类事情,将关键资源保留在可以跟踪它们的地方。
    • 线程本地存储中的项目通常本身是非托管资源;如果创建它们的代码在没有清除它们的情况下放弃了控制,但线程仍然存在(可能在线程池中),那么这些项目可能永远不会被清除。将 IDisposable 用于此类事情允许它们通过“使用”块进行管理,这些块本质上将正确处理线程。
    • 例如,可以使用创建线程静态 List 的代码包装 IDisposable 的构造函数,构造函数会将它创建的所有 IDisposable 对象复制到其中。如果构造函数正常完成,包装器可以销毁对列表的线程静态引用。否则,如果构造函数抛出异常,则包装器可以使用线程静态列表来清理由失败的构造函数创建的搁浅 IDisposables。不是一个完全干净的模式,但由于 C# 和 vb 都没有提供任何处理失败构造函数的好方法,这是我发现的最好的。
    • 这种模式将确保在字段初始化程序或基类构造函数中创建的任何 IDisposable 对象都将被清理,即使派生类构造函数抛出异常也是如此。该模式的唯一问题是,如果包装器对象没有被 Disposed,它将无法工作,并且如果正确的线程没有,包装器就无法使用终结器来执行清理。
    【解决方案2】:

    您需要处理它们。

    托管对象自己管理它们的内存。但内存并不是对象使用的唯一资源。 Dispose() 旨在释放其他资源。

    【讨论】:

    • 还有哪些资源?能详细点吗?
    • @sagar 文件句柄、数据库连接、网络连接等
    【解决方案3】:

    您的方法具有明显的讽刺意味。通过预先创建钢笔/画笔,您正是创建 Dispose() 试图解决的问题。那些 GDI 对象的存在时间会更长,就像您不调用 Dispose() 时一样。实际上更糟糕的是,它们至少会在表单关闭之前一直存在。

    他们可能已经足够长,可以晋升为第 2 代了。垃圾收集器不经常进行 gen#2 收集,现在对它们调用 Dispose() 变得更加重要。通过将表单的 Dispose() 方法从 Designer.cs 文件移动到您的 form.cs 文件并添加 Dispose 调用来实现。

    但是,这样做是正确的。钢笔和刷子是非常便宜的物品。当您需要它们时,在 Paint 事件中创建它们。并使用 using 语句,以便立即处理它们。使用 Stopwatch 类再次向自己保证这实际上不会导致任何减速。

    【讨论】:

      【解决方案4】:

      我编写了一个使用大量钢笔和画笔的 GDI+ 图表组件。我创建了它们并将它们放置在进行绘图的代码块中,性能从来都不是问题。更好的是在 OS 恕我直言中挂着一个长寿命的句柄。

      【讨论】:

        【解决方案5】:

        您是否对此进行了分析以查看创建和处置这些对象是否真的是一个问题?我不认为它是。

        只需遵循 create-in-a-using-block 模式,您就可以让事情变得更轻松,当然也更不容易出错。

        如果您确实想创建一次它们,那么还要在您拥有的类上实现 IDisposable 并在您拥有的对象上迭代 Dispose。不需要析构函数(终结器)。

        对实际上不需要 Dispose 的对象执行此操作几乎没有任何成本,但如果您在一个确实需要 Dispose 的对象上忘记了 Dispose,则成本会很高。

        【讨论】:

          【解决方案6】:

          不,IDisposable 适用于使用非托管资源的托管对象。 通常,您应该在完成后将它们丢弃。

          【讨论】:

            【解决方案7】:

            您确实需要查找有关画笔、钢笔等的文档。

            如果他们不使用非托管资源,您可能不必调用 Dispose。但是 using/Dispose 模式有时会被“误用”。例如,考虑 ASP.NET MVC 框架。在这里你可以这样写:

            using(Html.BeginForm(...)){
              ///HTML input fields etc.
            }
            

            Html.BeginForm(...)被调用时,会输出一个FORM标签。当 using 语句结束时,将在从 Html.BeginForm(...) 返回的对象上调用 Dispose。调用 Dispose 会导致结束 FORM 标记被呈现。通过这种方式,编译器实际上会强制执行 FORM 标记的配对,因此您不会忘记结束标记。

            【讨论】:

              【解决方案8】:

              不,Pens 和 Brushes 是不是完全托管的对象。

              它们包含非托管资源的句柄,即底层图形系统中相应的 GDI 对象。 (不确定这里的确切术语...)

              如果您不释放它们,则在垃圾收集器最终确定对象之前不会释放句柄,并且不能保证它会很快发生,或者根本不会发生。

              【讨论】:

                【解决方案9】:

                其他人提到了 GDI 对象的“使用”块 - 这是一个代码示例:

                using( var bm = new Bitmap() )
                using( var brush = new Brush() )
                {
                
                   // code that uses the GDI objects goes here
                   ...
                
                } // objects are automatically disposed here, even if there's an exception
                

                请注意,单个代码块可以有任意多的“使用”行。

                我认为这是一种处理 Disposable 对象的好方法。

                【讨论】:

                  【解决方案10】:

                  不,那是错误的。我同意 Aaronaught 的观点。

                  此外,Microsoft 在 Don Box 于 2003 年中期进行的一次网络广播中建议,每个 .Net 开发人员都应该处理自己的对象,无论是托管对象还是非托管对象,因为这可以将代码性能提高多达 20%。如果做得好,它可以显着提高性能。因此,它是每个 .net 开发人员都需要了解和使用的核心技能。

                  【讨论】:

                    【解决方案11】:

                    尽管您询问了钢笔和画笔,但字体是一个有一些奇怪怪癖的类。特别是,如果为了设置控件的 Font 属性而创建字体,则仍需负责处置该字体——所有权不会转移给控件——但可以通过在任何时候——甚至在创建字体后,在将其分配给控件之前。似乎 Font 是托管信息对象和非托管 GDI 资源的组合,对于某些目的,只需要前者。奇怪的设计——字体应该是两个类。

                    【讨论】:

                      猜你喜欢
                      • 2013-01-28
                      • 1970-01-01
                      • 2011-03-31
                      • 1970-01-01
                      • 1970-01-01
                      • 2010-10-14
                      • 1970-01-01
                      • 2011-03-10
                      • 1970-01-01
                      相关资源
                      最近更新 更多