c# ファイナライザーの実行タイミング

c# のファイナライザーは、c++ のデストラクターよりも早期に呼び出されることがあるようなので、かなり注意が必要です。下のコードを見てください。

public static void Test()
{
  new A().CallTest();
}

class A
{
  protected IntPtr handle;

  public A()
  {
    handle = new IntPtr(1);
    Console.WriteLine("A(): " + handle);
  }

  ~A()
  {
    handle = IntPtr.Zero;
    Console.WriteLine("~A(): " + handle);
  }
  
  public void CallTest()
  {
    Console.WriteLine("a.CallTest() 開始: " + handle);

    var b = new B(handle);
    new Thread(b.Exec).Start();

    GC.Collect();
    System.Threading.Thread.Sleep(1000);
    Console.WriteLine("a.CallTest() 終了");
  }
}

CallTest() メソッドで、b.Exec を実行するスレッドをスタートして終了する簡単なコードです。handle は、ウィンドウハンドルやビットマップハンドル、あるいは、ネイティブコード用のポインターを想定していて、実際のコードでは、~A() で開放します。

GC.Collect() では、強制的にガベージコレクションが実行されます。

実行にはあまり影響ありませんが、一応クラス B のコードも掲載します。クラス B のメソッド呼び出し部分は、実際のコードでは、ネイティブコードの呼び出しを想定しています。例えば、handle を引数に Windows API を呼びだします。

class B
{
  protected IntPtr handle;

  public B(IntPtr handle)
  {
    this.handle = handle;
  }

  public void Exec()
  {
    Test(handle);
  }

  static void Test(IntPtr handle)
  {
    System.Threading.Thread.Sleep(500);
    Console.WriteLine("b.Test(): " + handle);
  }
}

このコードの実行結果は、環境によって異なるでしょうが、c# 2008 Express Edition でコンパイル、Windows XP x64 のデバッグ版で実行すると、↓のようになります。

A(): 1
a.CallTest() 開始: 1
b.Test(): 1
a.CallTest() 終了
~A(): 0

ところが、驚くべきことに、リリース版では、結果が全く異なります。

A(): 1
a.CallTest() 開始: 1
~A(): 0
b.Test(): 1
a.CallTest() 終了

注目すべきは、a のメソッド CallTest() の実行が終わる前に、ファイナライザーが呼びだされている点です。 ~A()handle の開放を行っている場合、 b.Test()で は、 handle が無効になってしまいます。この例そのものは安全ですが、実際には、

var b = new B(handle);
new Thread(b.Exec).Start();

の部分で、ネイティブコードを呼びだすので、非常に危険です。アプリケーションが落ちたり、最悪な場合、有害なコードを実行してしまうかもしれません。

また、この現象は、 GC.Collect() で、無理に引き起こしていますが、 GC.Collect() が無くても起こります。このテストプログラムは小さいためにほぼ起きませんが、ある程度長く実行する実用的なプログラムでは、稀に起こります。

これを原因とする不具合は、デバッグ版など環境によっては絶対に起きない点、リリース版でも、タイミングによっては発生したりしなかったりするので、非常に厄介なので気をつけましょう。実際苦労しました・・・ (涙)。

不具合を回避する方法は、ネイティブリソースをイミュータブルクラスでラップする方法 か、 ネイティブリソースをラップする方法 をご覧ください。前者の方が効率的です。

となりのページ

このサイトについて

このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。

連絡先

ここをクリック すると表示されるページから作者へメールで連絡できます。

共有