using 構文使用時に Dispose が呼出されない確率

以前から何度か取り上げているネタですが,実際のどれぐらい起きるものか気になって試してみました.



下のように延々と new と Dispose を繰り返しているスレッドをランダムに Abort させてみると,非常に小さい確率ですが,new されたオブジェクトの Dispose 呼出しが行われないという現象が発生します.

while (true)
{
    using (MyDisposableObject obj = new MyDisposableObject())
    {
    }
}

手元の環境で試してみたところ,20,000 回の試行で 4 回ほど発生しました.0.02 % ぐらいです.これは using 構文の中身が空の場合の結果なので,実際にはもう何桁か発生確率は下がるかと思います.
ソースコードはこちら.
http://www.dwahan.net/nyaruru/hatena/UsingTest.zip
ちなみに Visual Studio 2005 Team Edition for Software Developers 付属の Code Profiler で,Instrumentation Profile *1 を使用すると,50% 近い確率で発生します.これぐらいになってくると,「using で確実に解放」なんて言えなくなってくるレベルですね.



以前も紹介しましたが(id:NyaRuRu:20050515:p6),これは非同期例外の問題です.

using (ResourceType resource = expression) statement

という C# の using 構文は,ResourceType が参照型のとき,次のコードと等価です.

{
    ResourceType resource = expression; // (1)
    try {
        statement;
    }
    finally {
        ((IDisposable)resource).Dispose();
    }
}

ここで問題なのは (1) の代入です.とりあえず expression が変数の場合を考えるとして,問題はこの代入が行われる間に非同期例外が割り込んだ場合に,Dispose が呼び出されないということです.
expression がメソッド呼出しだったりコンストラクタ呼出しだったりする場合にも,それらのメソッド内に例外が割り込む可能性がありますが,それらをメソッド内部での try-finally で対処したとしても,やはり最後の最後で代入時にごくわずかな隙は残ります.これは using 構文を使う限り避けられません.
なお,上の例では Dispose() メソッドの実行時には ThreadAbortException が割り込まれないことに注意してください.先日見たように CLR 2.0 では (id:NyaRuRu:20060602:p1) finally ブロック実行中のスレッドに対する Abort は,発生が遅延させられます.using 構文での Dispose メソッドは finally ブロックから呼び出されているので,Dispose メソッド実行中には外部からの Abort はブロックされることになります.
(追記)じゃあどうするかについては次のエントリへ.

*1:プログラムの動作を命令単位でトレースするために命令書き換えを行うプロファイリング方法