5

今夜、私は制約付き実行領域をいじって、より細かい部分の理解を深めました。以前にも時々使用したことがありますが、その場合は、ほとんどの場合、確立されたパターンに厳密に従っていました。とにかく、うまく説明できない奇妙なことに気づきました。

次のコードを検討してください。注: .NET 4.5 を対象とし、デバッガーをアタッチせずにリリース ビルドでテストしました。

public class Program
{
    public static void Main(string[] args)
    {
        bool toggle = false;
        bool didfinally = false;
        var thread = new Thread(
            () =>
            {
                Console.WriteLine("running");
                RuntimeHelpers.PrepareConstrainedRegions();
                try
                {
                    while (true) 
                    {
                      toggle = !toggle;
                    }
                }
                finally
                {
                    didfinally = true;
                }
            });
        thread.Start();
        Console.WriteLine("sleeping");
        Thread.Sleep(1000);
        Console.WriteLine("aborting");
        thread.Abort();
        Console.WriteLine("aborted");
        thread.Join();
        Console.WriteLine("joined");
        Console.WriteLine("didfinally=" + didfinally);
        Console.Read();
    }
}

このプログラムの出力はどうなると思いますか?

  1. didfinally=真
  2. didfinally=偽

推測する前に、ドキュメントを読んでください。以下に関連するセクションを含めます。

制約付き実行領域 (CER) は、信頼できるマネージド コードを作成するためのメカニズムの一部です。CER は、共通言語ランタイム (CLR) が帯域外例外をスローしないように制限されている領域を定義します。これにより、領域内のコードが完全に実行されなくなります。その領域内では、ユーザー コードは、帯域外例外のスローにつながるコードの実行を制限されます。PrepareConstrainedRegions メソッドは、try ブロックの直前に配置する必要があり、catch、finally、および fault ブロックを制約付き実行領域としてマークします。制約付き領域としてマークされると、コードは強力な信頼性コントラクトを持つ他のコードのみを呼び出す必要があり、コードが障害を処理する準備ができていない限り、準備されていないメソッドまたは信頼できないメソッドに割り当てたり、仮想呼び出しを行ったりすることはできません。CLR は、CER で実行されているコードのスレッド アボートを遅らせます。

信頼性の try/catch/finally は、アンマネージ バージョンと同じレベルの予測可能性を保証する例外処理メカニズムです。catch/finally ブロックは CER です。ブロック内のメソッドは事前の準備が必要であり、中断できないようにする必要があります。

私が今特に関心を持っているのは、スレッドのアボートを防ぐことです。Thread.Abort通常のバージョンと、CLR ホストが完全に中途半端になり、強制的に中止できるバージョンの2 種類があります。ブロックはすでにある程度finally保護されています。Thread.Abort次に、そのfinallyブロックをCERとして宣言すると、CLRホストの異常終了からの保護も強化されます...少なくともそれが理論だと思います。

したがって、私が知っていると思うことに基づいて、#1を推測しました。didfinally=True と表示されるはずです。ThreadAbortExceptionコードがまだブロック内にある間に が挿入され、 CER がなくてもtry、CLR によってfinallyブロックが期待どおりに実行されますか?

まあ、これは私が得た結果ではありません。まったく予想外の結果になりました。#1も#2も私には起こりませんでした。代わりに、私のプログラムは でハングしましたThread.Abort。これが私が観察するものです。

  • 遅延スレッドが存在すると、ブロックPrepareConstrainedRegions内でアボートします。try
  • が存在しないため、ブロックでPrepareConstrainedRegions使用できます。try

それで、百万ドルの質問はなぜですか?ドキュメントには、この動作についてはどこにも言及されていません。実際、私が読んでいるもののほとんどは、finally特にスレッドの異常終了を防ぐために、重要な割り込み不可能なコードをブロックに入れることを実際に示唆しています。

おそらく、ブロックに加えて、ブロック内PrepareConstrainedRegionsの通常のアボートを遅らせます。しかし、CLR ホストの中止は、CERのブロックでのみ遅延しますか? 誰かがこれについてもっと明確にすることができますか?tryfinallyfinally

4

3 に答える 3

2

少なくとも何が起こっているかについて理論を持っていると思います。ループが変更されてwhileスレッドがアラート可能状態になるThreadAbortExceptionと、CER セットアップを使用しても が注入されます。

RuntimeHelpers.PrepareConstrainedRegions();
try
{
   // Standard abort injections are delayed here.

   Thread.Sleep(1000); // ThreadAbortException can be injected here.

   // Standard abort injections are delayed here.
}
finally
{
    // CER code goes here.
    // Most abort injections are delayed including those forced by the CLR host.
}

そのため、ブロック内の whileから発行されたアボートをPrepareConstrainedRegions降格させて、 のように動作させます。これにより内部のコードが少し安全になる理由は簡単にわかるはずです。アボートは、データ構造が一貫した状態になる可能性が高いポイントに到達するまで延期されます。もちろん、これは、開発者が重要なデータ構造の更新中にスレッドを意図的に (または意図せずに) アラート可能状態にしないことを前提としています。Thread.AborttryThread.Interrupttry

そのため、基本的にPrepareConstrainedRegionsは、 の内部でアボートがいつ注入されるかをさらに制約する、文書化されていない機能が追加されていますtrytryこの機能は文書化されていないため、CER コンストラクトのブロックに重要なコードを配置しないことで、開発者がこの仮定に依存することを避けることが賢明です。文書化されているようcatchに、 、finally、およびfault(C# にはありません) ブロックのみが、CER のスコープとして正式に定義されています。

于 2013-08-29T19:06:54.117 に答える