17

この質問は、以前は.NET 4.0で機能していたコードが、.NET 4.5では未処理の例外で失敗したために発生しました。これは、一部はtry/finallyが原因です。詳細が必要な場合は、 Microsoftconnectで詳細をお読みください。この例のベースとして使用したので、参照すると役立つ場合があります。

コード

この質問の背後にある詳細について読まないことを選択した人々のために、これが起こった状況を非常に簡単に見てみましょう。

using(var ms = new MemoryStream(encryptedData))
using(var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
using(var sr = new StreamReader(cryptoStream))

Disposeこの問題は、CryptoStreamのメソッドからスローされる例外があることです(これらはusingステートメント内にあるため、これらの例外は2つの異なるfinallyブロックからスローされます)。cryptoStream.Dispose()によって呼び出されると、StreamReaderCryptographicExceptionスローされます。2回目cryptoStream.Dispose()は、usingステートメントで呼び出され、ArgumentNullException

次のコードは、上記のリンクから不要なコードのほとんどを削除し、usingステートメントをtry / finalsにアンワインドして、finallyブロックにスローされていることを明確に示します。

using System;
using System.Security.Cryptography;
namespace Sandbox
{
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                try
                {
                    try
                    {
                        Console.WriteLine("Propagate, my children");
                    }
                    finally
                    {
                        // F1
                        Console.WriteLine("Throwing CryptographicExecption");
                        throw new CryptographicException();
                    }
                }
                finally
                {
                    // F2
                    Console.WriteLine("Throwing ArgumentException");
                    throw new ArgumentException();
                }
            }
            catch (ArgumentException)
            {
                // C1
                Console.WriteLine("Caught ArgumentException");
            }
            // Same behavior if this was in an enclosing try/catch
            catch (CryptographicException)
            {
                // C2
                Console.WriteLine("Caught CryptographicException");
            }
            
            Console.WriteLine("Made it out of the exception minefield");
        }
    }}

注:try / finalは、参照されたコードの拡張されたusingステートメントに対応します。

出力:

    伝播する、私の子供たち
    CryptographicExecptionを投げる
    ArgumentExceptionをスローします
    ArgumentExceptionをキャッチしました
    何かキーを押すと続行します 。。。

CryptographicExceptioncatchブロックが実行されたことはないようです。ただし、そのcatchブロックを削除すると、例外によってランタイムが終了します。

もう少し情報

編集:これは仕様の最新リビジョンに更新されました。私がたまたまMSDNから入手したものは、古い表現でした。 Lostに更新されましたterminated

C#仕様の詳細については、セクション8.9.5および8.10で例外の動作について説明しています。

  • finallyブロック内を含め、例外がスローされると、制御は、それを囲むtryステートメントの最初のcatch節に移されます。これは、適切なステートメントが見つかるまでtryステートメントを続けます。
  • finallyブロックの実行中に例外がスローされ、例外がすでに伝播されていた場合、その例外は終了します。

「終了」は、最初の例外が2番目にスローされた例外によって永久に隠されているように見えますが、何が起こっているのかはわかりません。

質問はここのどこかにあると確信しています

ほとんどの場合、ランタイムが実行していることを視覚化するのは簡単です。F1コードは、例外がスローされる最初のfinallyブロック()まで実行されます。例外が伝播すると、2番目のfinallyブロック(F2)から2番目の例外がスローされます。

仕様によると、CryptographicExceptionスローされたfromF1は終了し、ランタイムはのハンドラーを探していますArgumentException。ランタイムはハンドラーを見つけ、ArgumentExceptionC1)のcatchブロック内のコードを実行します。

ここで霧が発生します。仕様では、最初の例外が終了することが示されています。ただし、2番目のcatchブロック(C2)がコードから削除された場合、CryptographicException失われたと思われるものは、プログラムを終了する未処理の例外になります。現在のところ、C2コードは未処理の例外で終了しないため、表面的には例外を処理しているように見えますが、ブロック内の実際の例外処理コードは実行されません。

質問

質問は基本的に同じですが、具体性のために言い換えられています。

  1. ブロックを削除すると例外が処理されずにランタイムが終了するため、finallyブロックを囲むことからスローされCryptographicExceptionた例外が原因でが終了するのはどうしてですか?ArgumentExceptioncatch (CryptographicException)

  2. CryptographicExceptionブロックが存在するときにランタイムが処理しているように見えるのにcatch (CryptographicException)、ブロック内のコードが実行されないのはなぜですか?


追加情報編集

私はまだこれの実際の振る舞いを調査しています、そして答えの多くは少なくとも上記の質問の一部に答えるのに特に役立ちました。

catch (CryptographicException)ブロックをコメントアウトした状態でコードを実行すると発生するもう1つの奇妙な動作は、.NET4.5と.NET3.5の違いです。.NET 4.5は、をスローしCryptographicExceptionてアプリケーションを終了します。ただし、.NET 3.5は、例外があるC#仕様に従って動作するようです。

伝播する、私の子供たち
CryptographicExecptionを投げる

未処理の例外:System.Security.Cryptography.CryptographicException [...]
ram.cs:23行目
ArgumentExceptionをスローします
ArgumentExceptionをキャッチしました
例外的な地雷原からそれを作りました

.NET 3.5では、仕様で読んだ内容がわかります。キャッチする必要があるのは。だけなので、例外は「失われた」または「終了した」になりますArgumentException。そのため、プログラムは実行を継続できます。私のマシンには.NET4.5しかありませんが、これは.NET 4.0で発生するのでしょうか?

4

3 に答える 3

8

.NETでの例外処理には、次の3つの異なる段階があります。

  • ステージ1は、throwステートメントが実行されるとすぐにギアを開始します。CLRは、例外を処理する意思があることをアドバタイズするスコープ内のキャッチブロックを探します。この段階では、C#ではコードは実行されません。技術的にはコードを実行することは可能ですが、その機能はC#では公開されていません。

  • キャッチブロックが特定され、CLRが実行の再開場所を認識すると、ステージ2が開始されます。次に、最終的にどのブロックを実行する必要があるかを確実に判断できます。すべてのメソッドスタックフレームも同様に巻き戻されます。

  • ステージ3は、finallyブロックがすべて完了し、catchステートメントを含むメソッドにスタックがアンワインドされると開始されます。命令ポインタは、catchブロックの最初のステートメントに設定されます。このブロックにそれ以上のthrowステートメントが含まれていない場合、catchブロックを過ぎたステートメントで通常どおり実行が再開されます。

したがって、コードスニペットのコア要件は、スコープにキャッチ(CryptographicException)があることです。これがないと、ステージ1は失敗し、CLRは実行を再開する方法を知りません。スレッドは停止しており、通常、例外処理ポリシーに応じてプログラムも終了します。finallyブロックはどれも実行されません。

ステージ2でfinallyブロックが例外をスローした場合、通常の例外処理シーケンスはすぐに中断されます。元の例外は「失われた」もので、ステージ3に到達することはないため、プログラムで監視することはできません。例外処理はステージ1から始まり、新しい例外を探し、そのfinallyブロックのスコープから開始します。

于 2012-08-28T01:31:43.693 に答える
6

finallyブロックの実行中に例外がスローされ、例外がすでに伝播されていた場合、その例外は失われます。

基本的に、実行すると何が起こりますか?

  • CryptographicExceptionいよいよインナーに投入されます。
  • アウタースコープがついに実行され、スローされArgumentExceptionます。この時点で「CryptographicException」は「伝播中」であったため、失われます。
  • 最終的なキャッチが発生し、キャッチされますArgumentException

...そして、別のfinallyブロックからスローされた別の例外があったという理由だけで、最初の例外が単にエーテルに消えるのは意味がありません。

あなたが引用したC#言語仕様に基づいて、これはまさに起こることです。最初の例外(CryptographicException)は事実上消えます-それは「失われました」。

ただし、明示的に使用することによってのみこの状態に到達できるfinallyため、この期待または可能性を念頭に置いてエラー処理を提供していると想定しています(tryその時点で使用しているため、受け入れたことを意味します)例外がある場合があります)。

これは基本的にの仕様で詳細に説明されています(引用8.9.5したテキストはこのセクションを参照しています):8.10

finallyブロックが別の例外をスローした場合、現在の例外の処理は終了します。

最初の例外は、あなたの場合ArgumentException、基本的に「消える」ことです。

于 2012-08-27T23:53:01.427 に答える
2

結局のところ、私は頭がおかしいわけではありません。この質問に対する私が得た答えに基づいて、私は仕様で非常に明確に概説されているものを理解するのに苦労しているように思えたと思います。把握するのは全く難しいことではありません。

真実は、動作がそうではなかったのに、仕様は理にかなっているということです。これはさらに多く見られ、古いランタイムでコードを実行すると、動作が完全に異なります...または少なくともそう見えます

簡単な要約

x64 Win7マシンで見たもの:

  • .NETv2.0-3.5-CryptographicExceptionがスローされたときのWERダイアログ。を押した後Close the program、実行がスローされなかったかのように、プログラムは続行されます。アプリケーションは終了しません。これは、仕様を読むことで期待される動作であり、.NETで例外処理を実装したアーキテクトによって明確に定義されています。

  • .NETv4.0-4.5-WERダイアログは表示されません。代わりに、プログラムをデバッグするかどうかを尋ねるウィンドウが表示されます。クリックするnoと、プログラムはすぐに終了します。その後、最終的にブロックは実行されません。

結局のところ、私の質問に答えようとする人のほとんどは、私とまったく同じ結果を得るでしょう。そのため、ランタイムが飲み込んだ例外でランタイムが終了した理由について、誰も私の質問に答えられなかったのはそのためです。

それはあなたが期待するものでは決してありません

Just-In-Timeデバッガーを誰が疑ったでしょうか?

.NET2でアプリケーションを実行すると.NET4とは異なるエラーダイアログが表示されることに気付いたかもしれませんが、私のように、開発サイクル中にそのウィンドウを期待するようになったので、そうではありませんでした。何でも考えてください。

vsjitdebugger実行可能ファイルは、アプリケーションを続行させるのではなく、アプリケーションを強制的に終了していました。2.0ランタイムでdw20.exeは、この動作はありません。実際、最初に表示されるのはそのWERメッセージです。

アプリケーションを終了するjitデバッガーのおかげで、実際には準拠しているのに、仕様に準拠していないように見えました。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Autoこれをテストするために、レジストリキーを1から0に変更して、vsjitdebuggerが失敗時に起動しないようにしました。確かに、アプリケーションは.NET 2.0と同様に、例外を無視して続行しました。

.NET4.0で実行


結局のところ、回避策がありますが、アプリケーションが終了しているため、この動作を回避する理由は実際にはありません。

  1. Just-In-Timeデバッガウィンドウが表示さManually choose the debugging enginesれたら、デバッグすることを確認して[はい]をクリックします。
  2. Visual Studioでエンジンオプションが表示されたら、[キャンセル]をクリックします。
  3. これにより、マシンの構成に応じて、プログラムが続行されるか、WERダイアログがポップアップ表示されます。その場合、プログラムを閉じるように指示しても実際には閉じられず、すべてが正常であるかのように実行され続けます。
于 2012-08-31T22:07:24.167 に答える