21

次のコードを使用します。

using System;

namespace OddThrow
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                throw new Exception("Exception!");
            }
            finally
            {
                System.Threading.Thread.Sleep(2500);
                Console.Error.WriteLine("I'm dying!");
                System.Threading.Thread.Sleep(2500);
            }
        }
    }
}

これにより、次の出力が得られます。

Unhandled Exception: System.Exception: Exception!
   at OddThrow.Program.Main(String[] args) in C:\Documents and Settings\username
\My Documents\Visual Studio 2008\Projects\OddThrow\OddThrow\Program.cs:line 14
I'm dying!

私の質問は:なぜ未処理の例外テキストが最終の前に発生するのですか? 私の考えでは、この例外が処理されていないことを知る前に、スタックが巻き戻されるときに finally を実行する必要があります。Sleep() の呼び出しに注意してください。これらは、ハンドルされていない例外が出力された後に発生します。

  1. 未処理の例外テキスト/メッセージ
  2. 最後にブロックします。
  3. アプリケーションを終了する

C# 標準の §8.9.5 によると、この動作は正しくありません。

  • 現在の関数メンバーで、スロー ポイントを囲む各 try ステートメントが調べられます。各ステートメント S について、最も内側の try ステートメントから始まり、最も外側の try ステートメントで終わるまで、次のステップが評価されます。
    • S の try ブロックがスロー ポイントを囲み、S に 1 つ以上の catch 句がある場合、catch 句は出現順に調べられ、例外に適したハンドラーが検索されます。例外の種類または例外の種類の基本型を指定する最初の catch 句は、一致と見なされます。一般的な catch 句 (§8.10) は、あらゆる例外タイプに一致すると見なされます。一致する catch 句が見つかった場合、その catch 句のブロックに制御を移すことによって、例外の伝播が完了します。
    • それ以外の場合、S の try ブロックまたは catch ブロックがスロー ポイントを囲み、S に finally ブロックがある場合、制御は finally ブロックに転送されます。finally ブロックが別の例外をスローした場合、現在の例外の処理は終了します。それ以外の場合、制御が finally ブロックの終点に到達すると、現在の例外の処理が続行されます。
  • 現在の関数メンバー呼び出しで例外ハンドラーが見つからなかった場合、関数メンバー呼び出しは終了します。上記の手順は、関数メンバーが呼び出されたステートメントに対応するスロー ポイントを使用して、関数メンバーの呼び出し元に対して繰り返されます。
  • 例外処理が現在のスレッドのすべての関数メンバー呼び出しを終了し、スレッドに例外のハンドラーがないことを示す場合、スレッド自体が終了します。このような終了の影響は実装定義です。

どこが間違っていますか?(私はいくつかのカスタム コンソール エラー メッセージを持っていますが、これは邪魔です。些細なことで、面倒で、言語に疑問を抱かせます...)

4

9 に答える 9

7

実行の順序に関する規格の記述は正しく、観察しているものと矛盾していません。「未処理の例外」メッセージは、実際には例外ハンドラ自体ではなく、CLRからの単なるメッセージであるため、プロセスのどの時点でも表示できます。実行順序に関する規則は、CLR自体ではなく、CLR内で実行されるコードにのみ適用されます。

実際に行ったことは、実装の詳細を公開することです。つまり、未処理の例外は、ルートまで実際に探索するのではなく、内部にあるtry{}ブロックのスタックを調べることによって認識されます。例外は、このスタックを確認することで処理される場合と処理されない場合がありますが、未処理の例外はこの方法で認識されます。

ご存知かもしれませんが、メイン関数にトップレベルのtry {} catch {}を配置すると、期待どおりの動作が見られます。次のフレームで一致するキャッチをチェックする前に、各関数のfinallyが実行されます{ }。

于 2009-05-19T20:53:11.727 に答える
2

出力は、実際にはデフォルトのCLR例外ハンドラーからのものです。例外ハンドラーは、finallyブロックの前に発生します。finallyブロックの後、CLRは未処理の例外のために終了します(c#はfinally句が呼び出されることを[1]保証するため、前に終了することはできません)。

したがって、これは単なる標準的な動作であり、例外処理は最終的に発生する前に発生します。

[1]少なくとも内部ランタイムエラーまたは停電がない場合、通常の操作中に保証されます

于 2009-05-19T20:36:18.017 に答える
2

ミックスにさらに追加するには、次のことを考慮してください。

using System;
namespace OddThrow
{
    class Program
    {
        static void Main()
        {
            AppDomain.CurrentDomain.UnhandledException +=
                delegate(object sender, UnhandledExceptionEventArgs e)
            {
                Console.Out.WriteLine("In AppDomain.UnhandledException");
            };
            try
            {
                throw new Exception("Exception!");
            }
            catch
            {
                Console.Error.WriteLine("In catch");
                throw;
            }
            finally
            {
                Console.Error.WriteLine("In finally");
            }
        }
    }
}

私のシステム(ノルウェー語)でこれを示しているのはどれですか?

[C:\..] ConsoleApplication5.exe
In catch
In AppDomain.UnhandledException

Ubehandlet unntak: System.Exception: Exception!
   ved OddThrow.Program.Main() i ..\Program.cs:linje 24
In finally
于 2009-05-19T20:49:40.563 に答える
2

私が物事を読んでいる方法では、ベースから外れている可能性があります...しかし、キャッチせずに最終的にブロックしてみてください。

投稿した説明から判断すると、例外はキャッチされないため、呼び出し元にバブルアップし、スタックを上って動作し、最終的には処理されず、呼び出しが終了し、最後にブロックが呼び出されます。

于 2009-05-19T20:35:36.350 に答える
1

完全に期待されているわけではありませんが、プログラムは正常に動作します。finallyブロックは最初に実行されることは期待されておらず、常に実行されることが期待されているだけです。

サンプルを調整しました:

public static void Main()
{
    try
    {
        Console.WriteLine("Before throwing");
        throw new Exception("Exception!");
    }
    finally
    {
        Console.WriteLine("In finally");
        Console.ReadLine();
    }
}

この場合、厄介な未処理の例外ダイアログが表示されますが、その後、コンソールは出力して入力を待機するため、Windows自体が未処理の例外をキャッチする直前ではなく、finallyを実行します。

于 2009-05-19T20:37:33.617 に答える
0

キャッチなしのtry/finalは、表示されているとおりに実行するデフォルトのハンドラーを使用します。私はいつもそれを使用しています。たとえば、例外の処理でエラーがカバーされる場合でも、実行したいクリーンアップがまだある場合などです。

また、標準エラーへの出力と標準出力がバッファリングされることにも注意してください。

于 2009-05-19T20:37:01.247 に答える
0

try-catch-finally ブロックは、ある時点でキャッチされた場合、期待どおりに機能します。このためのテスト プログラムを作成し、さまざまなネスト レベルを使用したとき、説明した内容と一致する方法で動作した唯一のケースは、例外がコードによって完全に処理されず、オペレーティング システムにバブル アウトした場合でした。

実行するたびに、OS がエラー メッセージを作成しました。したがって、問題はC#ではなく、ユーザーコードによって処理されないエラーがアプリケーションの制御下にないため、ランタイム(私は信じています)がそれに実行パターンを強制できないという事実にあります。

Windows フォーム アプリケーションを作成し、コンソールに直接書き込むのではなく、すべてのメッセージをテキスト ボックスに書き込んだ (その後すぐにフラッシュした) 場合、そのエラー メッセージはエラー コンソールに挿入されているため、まったく表示されませんでした。独自のコードではなく、呼び出し元のアプリケーションによって。

編集

その重要な部分を強調しようと思います。未処理の例外は制御できないため、例外ハンドラーがいつ実行されるかを判断することはできません。アプリケーションのある時点で例外をキャッチすると、finallyブロックはスタック内の下位ブロックの前に実行されますcatch

于 2009-05-19T20:29:46.797 に答える
0

いくつかの答えをまとめると、未処理の例外が発生するとすぐに、AppDomain で UnhandledExceptionEvent が発生し、コードは引き続き実行されます (つまり、finally)。これは、イベントに関する MSDN の記事です。

于 2009-05-19T21:02:43.860 に答える
0

次の試行:

  1. このケースは C# 標準では言及されていないと思いますが、ほとんど矛盾しているように見えることに同意します。

  2. これが発生する内部的な理由は、次のようなものだと思います。CLR は、デフォルトの例外ハンドラーを SEH ハンドラーとして FS:[0] に登録します。コードにキャッチが増えると、それらのハンドラーが SEH チェーンに追加されます。または、SEH 処理中に CLR ハンドラーのみが呼び出され、CLR 例外チェーンを内部で処理しますが、どれかはわかりません。

例外がスローされたときのコードでは、デフォルトのハンドラーのみが SEH チェーンにあります。このハンドラーは、スタックの展開が開始される前に呼び出されます。

デフォルトの例外ハンドラは、スタックに例外ハンドラが登録されていないことを認識しています。したがって、登録されているすべての UnhandledException ハンドラを最初に呼び出してから、エラー メッセージを出力し、AppDomain をアンロードするようにマークします。

そのスタックのアンロールが開始されてから、c# 標準に従ってブロックが呼び出されます。

私が見たように、CLR が未処理の例外を処理する方法は、c# 標準では考慮されておらず、スタックの展開中に finals が呼び出される順序のみが考慮されています。この順序は保持されます。その後、「そのような終了の影響は実装定義です」。条項が発効します。

于 2009-05-19T21:09:34.457 に答える