【问题标题】:C# Destructor not working as expectedC# 析构函数没有按预期工作
【发布时间】:2011-03-30 19:10:35
【问题描述】:

请看下面的代码。我希望它打印 10 个,因为我已经明确调用了垃圾收集器。但我总是得到 0 或 20 作为输出。这是为什么呢?

void Main()
{
    Panda[] forest_panda = new Panda[10];
    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    for(int i=0; i<forest_panda.GetLength(0);i++)
    {
        forest_panda[i]=new Panda("P1");
    }

    System.GC.Collect();

    Console.WriteLine("Total Pandas created is {0}",Panda.population);          
}

class Panda
{
    public static int population=0;
    public string name;

    public Panda(string name)
    {
        this.name = name;
        population = population + 1;
    }

    ~Panda()
    {
        population = population - 1;
    }   
}

请注意,Main 类是由 LINQPad(随“C# 4.0 in a Nutshell”一书附带的编辑器)自动创建的。我是 C# 新手。

【问题讨论】:

  • blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx Raymond 博客首页上还有更多好文章。
  • 为什么当你创建 20 个 Pandas 时它会打印 10 个?
  • @Rune FS 我没想到第二组 10 个 Pandas 会被 GCed。但现在我明白了它也可以被 GC,因为当我调用 GC 时,程序中不再引用它。

标签: c# garbage-collection destructor


【解决方案1】:

您尚未运行显式垃圾回收。来自GC.Collect()的文档:

使用此方法尝试回收 所有无法访问的内存。 但是,Collect 方法不 保证所有不可访问的内存 被回收。

所有对象,无论多长时间 他们一直在记忆中,是 考虑收集;然而, 在托管中引用的对象 不收集代码。用这个 强制系统尝试的方法 收回最大金额 可用内存。

垃圾收集器经过高度优化,在实际进行垃圾收集然后调用终结器时完全由自己“决定”。此外,这一切都是异步完成的。这也是终结器被称为非确定性清理的原因。当清理发生时,你永远不会现在。

您现在有两个选择。您可以调用GC.WaitForPendingFinalizers(),这将暂停当前线程,直到所有可终结对象都已完成。或者调用这个新的重载:System.GC.Collect(int generation, System.GCCollectionMode mode)GCCollectionMode.Forced 它是在 .NET 3.5 中引入的。

请记住,通常没有必要,更重要的是:一个坏主意手动调用垃圾收集器。此外,仅在极少数情况下才需要实现终结器。调用垃圾收集器会减慢运行时间。实现终结器会额外减慢运行时间。 garabge 收集器将所有实现终结器的对象放入终结队列,当它们准备好被 garabge 收集时。处理这个队列很昂贵。 更糟糕的是,当终结器运行时,不能保证您尝试访问的成员仍然存在。它们很可能已经被抢夺收集了。这就是为什么只有当您有非托管资源需要清理时才应该使用终结器。

您的示例中肯定不需要所有这些。您真正想要的是 IDisposable 进行确定性清理。

【讨论】:

  • 因为有时垃圾收集器“决定”现在是垃圾收集的好时机,幸运的是所有未决终结器都已执行。
  • 是否存在不需要调用垃圾收集器但仍会调用析构函数的情况
【解决方案2】:

这里有几点需要注意:

首先,发布版本和调试版本之间的 GC 行为不同。通常,在发布模式下,对象可以比在调试模式下更快地回收。

正如 Tim 指出的那样,调用 GC.Collect 不会调用终结器。如果您想等待终结器运行,请同时调用 GC.WaitForPendingFinalizers。

终结器由专用线程运行,因此您实际上是在没有任何同步的情况下从两个不同的线程修改状态。虽然在这种特殊情况下这可能不是问题,但这样做并不是一个好主意。但是在你去向你的终结器添加同步之前,请记住一个死锁的终结器意味着不再有终结器运行,因此这些对象的内存不会被回收。

【讨论】:

  • 我从来不知道这里会出现同步问题!没有我创建任何东西的线程!只有在使用终结器时才会出现问题,还是在使用静态变量时必须注意它们?
  • 这种情况下的问题是因为终结器是由运行时创建的专用线程运行的。
【解决方案3】:

在垃圾收集后尝试添加 System.GC.WaitForPendingFinalizers。
http://www.developer.com/net/csharp/article.php/3343191/C-Tip-Forcing-Garbage-Collection-in-NET.htm

【讨论】:

    【解决方案4】:

    您创建了 20 个对象,因此值将是 20。显式调用 System.GC.Collect() 实际上并不能保证调用析构函数。因此,如果调用它,所有 20 个对象可能都已被破坏,也可能没有。

    这解释了实际发生的事情。

    创建析构函数或显式调用 GC.Collect 不是好习惯。

    如果一个对象需要做清理,它应该实现 IDisposable

    【讨论】:

      【解决方案5】:

      在 .NET 中,对象生命周期为 non-deterministic,并且其行为与您对 C++ 构造函数/析构函数的预期不同。事实上,.NET 对象在技术上没有析构函数。终结器的不同之处在于它预计会在对象的生命周期内清理对象使用的非托管资源。

      要确定释放对象使用的资源的方式,您需要实现 IDisposable 接口。 IDisposable 并不完美,因为它仍然需要调用代码在完成后正确处理对象,并且很难处理对 Dispose 的意外多次调用。然而,C# 中的语法使这通常非常容易。

      class Panda : IDisposable
      {
          public static int population = 0;
          public string _name;
      
          public Panda( string name )
          {
              if( name == null )
                  throw new ArgumentNullException( name );
      
              _name = name;
              population++;
          }
      
          protected virtual void Dispose( bool disposing )
          {
              if( disposing && name != null )
              {
                  population--;
                  name = null;
              }
          }
      
          public void Dispose()
          {
              Dispose( true );
              GC.SuppressFinalize( this );
          }
      
          ~Panda(){ Dispose( false ); }
      }
      

      然后使用类:

      using( var panda = new Panda( "Cute & Cuddly" ) )
      {
          // Do something with the panda
      
      } // panda.Dispose() called automatically
      

      【讨论】:

      • 是的,我在思考 C++ 析构函数的思路,所以很困惑。谢谢..
      【解决方案6】:

      在 C# 中使用析构函数(也称为终结器)并不是真正的好方法。即使您显式调用垃圾收集器,也不能保证终结器会运行。您也不应该尝试强制垃圾回收,因为它可能会对您的应用程序的整体性能产生负面影响。

      相反,如果您需要显式释放对象拥有的资源,则应实现IDisposable 接口,并将清理逻辑放在 Dispose() 方法中。相反,当您使用实现 IDisposable 的对象时,您应该始终注意在完成后调用它的 Dispose() 方法。 C# 为此提供了“使用”语句。

      许多执行 I/O 的类(例如 Streams)都实现了 IDisposable。这是使用 FileStream 读取文本文件的示例。请注意“使用”语句,以确保在我们使用完 FileStream 后将其释放:

      using (FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
      {
          // Read a text file 1024 bytes at a time and write it to the console
          byte[] b = new byte[1024];
          while (fs.Read(b, 0, b.Length) > 0)
          {
              Console.WriteLine(Encoding.UTF8.GetString(b));
          }
      } // Dispose() is called automatically here
      

      上面的代码等价于:

      FileStream fs = File.OpenRead("C:\\temp\\myfile.txt"))
      try
      {
          // Read a text file 1024 bytes at a time and write it to the console
          byte[] b = new byte[1024];
          while (fs.Read(b, 0, b.Length) > 0)
          {
              Console.WriteLine(Encoding.UTF8.GetString(b));
          }
      }
      finally
      {
          fs.Dispose();
      }
      

      【讨论】:

        【解决方案7】:

        Disposing Pattern 是最好用的。 这是您工作的完整实现。

        请记住,您必须像下面的代码中那样自己调用 Dispose。

            public static void Main()
            {
                Panda[] forest_panda = new Panda[10];
                for (int i = 0; i < forest_panda.GetLength(0); i++)
                    forest_panda[i] = new Panda("P1");
        
                // Dispose the pandas by your own
                foreach (var panda in forest_panda)
                    panda.Dispose();
        
        
                for (int i = 0; i < forest_panda.GetLength(0); i++)
                    forest_panda[i] = new Panda("P1");
        
                // Dispose the pandas by your own
                foreach (var panda in forest_panda)
                    panda.Dispose();
        
                Console.WriteLine("Total Pandas created is {0}", Panda.population);
            }
        
            class Panda : IDisposable
            {
                public static int population = 0;
                public string name;
        
                public Panda(string name)
                {
                    this.name = name;
                    population = population + 1;
                }
        
                ~Panda()
                {
                    Dispose(false);
                }
        
                /// <summary>
                /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
                /// </summary>
                /// <filterpriority>2</filterpriority>
                public void Dispose()
                {
                    Dispose(true);
                    GC.SuppressFinalize(this);
                }
        
                private void Dispose(bool disposing)
                {
                    if (disposing)
                    {
                        population = population - 1;
                    }
                }
            }
        

        【讨论】:

          【解决方案8】:

          它在 release build 中为我调用。但是在调试中表现不同。

          【讨论】:

            猜你喜欢
            • 2020-04-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多