【问题标题】:How do I ensure that objects are disposed of properly in .NET?如何确保在 .NET 中正确处理对象?
【发布时间】:2010-01-11 01:12:32
【问题描述】:

我使用 C# 在 .NET 2 中创建了一个Windows Forms 应用程序,该应用程序可以连续运行。对于大多数帐户,我对此感到满意,但有人向我报告说它偶尔会失败。我能够在 50% 的时间内监控它的性能,而且我从未发现任何故障。

此时我担心该程序可能使用了太多资源,并且在不再需要资源时没有释放它们。

正确处置已创建计时器和图形对象(如图形路径、SQL 连接等)的已创建对象的最佳做法是什么?或者我可以依靠 dispose 方法来处理所有垃圾回收吗?

还有: 有没有办法可以监控应用程序使用的资源?

【问题讨论】:

  • 你的代码是不是都是托管代码没有unsafe、dllimport等等?
  • 那些关于失败的报告,它们是否包含异常信息,或者任何可以用来重现问题的东西?整个应用程序是否失败,或者只是一个方法调用?您可以尝试侦听 AppDomain.UnhandledException 以记录任何未处理的异常,但任何静默 catch {} 都被视为已处理,因此不会被记录。

标签: c# .net


【解决方案1】:

最佳做法是确保在不再需要对象时立即调用实现IDisposable 接口的所有对象。

这可以通过using 关键字或try/finally 构造来完成。

在为表单的生命周期分配资源的 WinForms 表单中,需要一种稍微不同的方法。由于表单本身实现了 IDisposable,这表明在某个时间点将在此表单上调用 Dispose。您想确保您的一次性资源同时得到处置。为此,您应该覆盖表单Dispose(bool disposing) 方法。实现应该如下所示:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        // dispose managed resources here
    }
    // dispose unmanaged resources here
}

关于表单组件的说明:如果您的对象实现了IComponent 接口,您可以将实例放在表单Container 中。当容器本身被释放时,容器会负责释放组件。

【讨论】:

  • 嗨,它不允许我处理它。它说“已经声明了具有相同签名的成员”。你如何覆盖从 Form 继承的 Dispose ?谢谢
  • 我认为我必须使用与 Designer 代码中相同的覆盖方法?
  • Kave - 您是否在 .Designer.cs 文件中看到了像上面示例中的 Dispose 方法?如果是这样,它在做什么?您可以将其作为问题发布在这里,以获得有关可以做什么的更多反馈......
【解决方案2】:

除了已经说过的,如果您使用 COM 组件,最好确保它们已完全发布。我有一个一直用于 COM 发布的 sn-p:

private void ReleaseCOMObject(object o)
{
   Int32 countDown = 1;
   while(countDown > 0)
       countDown = System.Runtime.InteropServices.Marshal.ReleaseCOMObject(o);
}

【讨论】:

  • 不错。而且我猜 COM 对象将被视为非托管资源,对吧?
【解决方案3】:

您应该在稀缺资源上致电Dispose 以释放它们。您可以为此使用using 声明:

using (var resource = new MyScarceObject()) 
{
    // resource will be used here...
} // will free up the resources by calling Dispose automatically

【讨论】:

    【解决方案4】:

    有几种方法可以确保这一点。我找到的主要帮助是利用“使用”关键字。这是这样应用的:

    using(SqlConnection connection = new SqlConnection(myConnectionString))
    {
        /* utilise the connection here */ 
    }
    

    这基本上翻译成:

    SqlConnection connection = null;
    try
    {
        connection = new SqlConnection(myConnectionString);
    }
    finally
    {
        if(connection != null) connection.Dispose();
    }
    

    因此,它仅适用于实现 IDisposable 的类型。

    此关键字在处理 GDI 对象(如钢笔和画笔)时非常有用。但是,在某些情况下,您可能希望将资源保留更长的时间,而不仅仅是方法的当前范围。作为一项规则,如果可能,最好避免这种情况,但例如在处理 SqlCe 时,保持与数据库的一个连接持续打开会更高效。因此,人们无法逃避这种需求。

    在这种情况下,您不能使用“使用”,但您仍然希望能够轻松回收连接所持有的资源。 您可以使用两种机制来取回这些资源。

    一个是通过终结器。所有超出范围的托管对象最终都由垃圾收集器收集。如果你定义了一个终结器,那么 GC 将在收集对象时调用它。

    public class MyClassThatHoldsResources
    {
        private Brush myBrush;
    
        // this is a finaliser
        ~MyClassThatHoldsResources()
        {
           if(myBrush != null) myBrush.Dispose();
        }
    }
    

    然而,不幸的是,上面的代码是废话。原因是在最终确定时您无法保证哪些托管对象已被收集,哪些尚未收集。因此,上面示例中的“myBrush”可能已经被垃圾收集器丢弃了。因此最好不要使用终结器来收集托管对象,它的用途是整理非托管资源。

    终结器的另一个问题是它不是确定性的。例如,假设我有一个通过串行端口进行通信的类。一次只能打开一个到串行端口的连接。因此,如果我有以下课程:

    class MySerialPortAccessor
    {
        private SerialPort m_Port;
    
        public MySerialPortAccessor(string port)
        {
            m_Port = new SerialPort(port);
            m_Port.Open();
        }
    
        ~MySerialPortAccessor()
        {
            if(m_Port != null) m_Port.Dispose();
        }
    }
    

    如果我这样使用对象:

    public static void Main()
    {
        Test1();
        Test2();
    }
    
    private static void Test1()
    {
        MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
        // do stuff
    }
    
    private static void Test2()
    {
        MySerialPortAccessor port = new MySerialPortAccessor("COM1:");
        // do stuff
    }
    

    我会遇到问题。问题是终结者不是确定性的。也就是说我不能保证它什么时候会运行,因此可以处理我的串口对象。因此,当我运行测试 2 时,我可能会发现端口仍然打开。 虽然我可以在 Test1() 和 Test2() 之间调用 GC.Collect() 来解决这个问题,但不推荐。如果您想从收集器中获得最佳性能,那就让它自己做吧。

    因此,我真正想做的是:

    class MySerialPortAccessor : IDispable
    {
        private SerialPort m_Port;
    
        public MySerialPortAccessor(string port)
        {
            m_Port = new SerialPort(port);
            m_Port.Open();
        }
    
        public void Dispose()
        {
            if(m_Port != null) m_Port.Dispose();
        }
    }
    

    我会这样重写我的测试:

    public static void Main()
    {
        Test1();
        Test2();
    }
    
    private static void Test1()
    {
        using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
        {
            // do stuff
        }
    }
    
    private static void Test2()
    {
        using( MySerialPortAccessor port = new MySerialPortAccessor("COM1:"))
        {
            // do stuff
        }
    }
    

    现在可以使用了。 那么终结者呢?为什么要使用它?

    非托管资源和不调用 Dispose 的可能实现。

    作为其他人使用的组件库的编写者;他们的代码可能会忘记处理对象。其他东西可能会终止该进程,因此 .Dispose() 不会发生。由于这些情况,应实施终结器以将任何非托管资源作为“最坏情况”的情况进行清理,但 Dispose应该整理这些资源,以便您拥有“确定性清理”例程。

    所以最后,.NET Framework Guidelines book 中推荐的模式是按以下方式实现两者:

    public void SomeResourceHoggingClass, IDisposable
    {
        ~SomeResourceHoggingClass()
        {
            Dispose(false);
        }
    
        public void Dispose()
        {
            Dispose(true);
        }
    
        // virtual so a sub class can override it and add its own stuff
        //
        protected virtual void Dispose(bool deterministicDispose)
        {    
             // we can tidy managed objects
             if(deterministicDispose)
             {
                  someManagedObject.Parent.Dispose();
                  someManagedObject.Dispose();
             }
    
             DisposeUnmanagedResources();
    
             // if we've been disposed by .Dispose()
             // then we can tell the GC that it doesn't
             // need to finalise this object (which saves it some time)
             //
             GC.SuppressFinalize(this);
        }
    }
    

    【讨论】:

      【解决方案5】:

      一些提示:

      -尽可能利用 Using() 关键字。 如果您有单元测试,我建议您重构代码以实现此更改。

      http://msdn.microsoft.com/en-us/library/yh598w02.aspx

      -记住显式取消注册所有事件处理程序并从列表中删除所有对象,这些对象在整个应用程序期间都存在。这是程序员在 .NET 中最常犯的错误,导致这些项目未被收集。

      【讨论】:

        【解决方案6】:

        至于监控内置perfmon (2) 可以很好地解决内存使用等问题。如果您担心文件句柄、dll 句柄等,我推荐Process ExplorerProcess Monitor

        【讨论】:

          【解决方案7】:

          当一个对象不可访问时,Object.Finalize Method 将被调用。在实现 IDisposable 的类中抑制这种不必要的调用会很有帮助。您可以拨打GC.SuppressFinalize Method

          public void Dispose()
          {
              // dispose resources here
          
              GC.SuppressFinalize(this);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-08-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-12-13
            相关资源
            最近更新 更多