スロー対rethrow:同じ結果?
-
30-09-2019 - |
質問
ネット上の多くのドキュメントを参照してください。 C#の例外を再投与する適切な方法は何ですか?「スローE」には違いがあるはずです。と「スロー;」。
しかし、から: http://bartdesmet.net/blogs/bart/archive/2006/03/12/3815.aspx,
このコード:
using System;
class Ex
{
public static void Main()
{
//
// First test rethrowing the caught exception variable.
//
Console.WriteLine("First test");
try
{
ThrowWithVariable();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
//
// Second test performing a blind rethrow.
//
Console.WriteLine("Second test");
try
{
ThrowWithoutVariable();
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
}
}
private static void BadGuy()
{
//
// Some nasty behavior.
//
throw new Exception();
}
private static void ThrowWithVariable()
{
try
{
BadGuy();
}
catch (Exception ex)
{
throw ex;
}
}
private static void ThrowWithoutVariable()
{
try
{
BadGuy();
}
catch
{
throw;
}
}
}
次の結果を示します。
$ /cygdrive/c/Windows/Microsoft.NET/Framework/v4.0.30319/csc.exe Test.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.
$ ./Test.exe
First test
at Ex.ThrowWithVariable()
at Ex.Main()
Second test
at Ex.ThrowWithoutVariable()
at Ex.Main()
これはブログ投稿と完全に矛盾しています。
次のコードで同じ種類の結果が得られます。 http://crazorsharp.blogspot.com/2009/08/Rethrowing-Exception-wethout-resetting.html
元の質問 :私は何が間違っているのですか?
アップデート :.NET 3.5 / csc.exe 3.5.30729.4926で同じ結果
sumup :あなたの答えはすべて素晴らしかったです、ありがとう。
そのため、64ビットジッターのために、その理由は効果的にインライン化されています。
私は1つの答えだけを選択しなければなりませんでした、そしてここに私が選択した理由があります ルーク 答え :
彼は、インランスの問題とそれが私の64ビットアーキテクチャに関連している可能性があると推測しました。
彼は、この動作を回避する最も簡単な方法であるノインリニングフラグを提供しました。
しかし、この問題は今や別の質問を上げています: この動作は、すべての.NET仕様に準拠していますか?
アップデート :この最適化は、次のように準拠しているようです。 スロー対rethrow:同じ結果? (ありがとう 0xa3)
よろしくお願いします。
解決
問題を再現することはできません - .NET 3.5(32ビット)を使用すると、BARTの記事で説明されているのと同じ結果が得られます。
私の推測では、.NET 4コンパイラ/ジッター - または、これが3.5の下で起こっている場合は64ビットコンパイラ/ジッターである可能性があると思います。 BadGuy
呼び出し方法への方法。以下を追加してみてください MethodImpl
属性 BadGuy
そして、それが違いを生むかどうかを確認してください:
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void BadGuy()
{
//
// Some nasty behavior.
//
throw new Exception();
}
他のヒント
私はこのコードを自分で実行しようとしましたが、デバッグビルドは予想どおりに機能しますが、リリースビルドであなたと同じ結果が得られました。
私は何が起こっているのかと思われます。コンパイラインラインが単にbadguy()call with throwを置き換えたのではないかと疑っています new Exception();
それがBadGuy()の唯一の声明だからです。
プロジェクトプロパティ - >ビルド画面の「最適化コード」オプションをオフにすると、リリースとデバッグビルドの両方が、スタックトレースの上部にBADGUY()を表示する同じ結果を生成します。
JITオプティマザーはここでいくつかの作業を行っているようです。ご覧のとおり、2番目のケースのコールスタックは、デバッグビルドを実行するときの最初のケースとは異なります。ただし、リリースビルドでは、最適化のために両方のコールスタックが同一です。
これがジッターに関連していることを確認するために、 MethodImplAttribute
属性:
[MethodImpl(MethodImplOptions.NoOptimization)]
private static void ThrowWithoutVariable()
{
try
{
BadGuy();
}
catch
{
throw;
}
}
ILはまだ異なることに注意してください ThrowWithoutVariable
と ThrowWithVariable
:
.method private hidebysig static void ThrowWithVariable() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] class [mscorlib]System.Exception ex)
.try
{
IL_0000: call void Ex::BadGuy()
IL_0005: leave.s IL_000a
} // end .try
catch [mscorlib]System.Exception
{
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: throw
} // end handler
IL_000a: ret
} // end of method Ex::ThrowWithVariable
.method private hidebysig static void ThrowWithoutVariable() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.try
{
IL_0000: call void Ex::BadGuy()
IL_0005: leave.s IL_000a
} // end .try
catch [mscorlib]System.Object
{
IL_0007: pop
IL_0008: rethrow
} // end handler
IL_000a: ret
} // end of method Ex::ThrowWithoutVariable
これがCLI仕様に準拠しているかどうかをフォローアップの質問に答えるための更新
実際、それは準拠しています。つまり、JITコンパイラが重要な最適化を可能にすることです。 付録f 52ページの状態(私による強調):
一部のCIL命令は、メモリとタイプの安全性を確保する暗黙の実行時間チェックを実行します。 もともと、CLIは、例外が正確であることを保証しました, 、つまり、例外がスローされたときにプログラム状態が保存されたことを意味します。 ただし、暗黙的なチェックの正確な例外を実施することで、いくつかの重要な最適化が実際に適用することが不可能になります。プログラマーは、カスタム属性を介してメソッドが「リラックス」されていることを宣言できるようになりました。
リラックスしたチェックは、指示を並べ替える最適化を許可しながら、検証可能性を保持します(メモリとタイプの安全性を保存することにより)。特に、次の最適化を可能にします。
- ループからの暗黙の実行時間チェックを巻き上げます。
- ループイテレーションの並べ替え(たとえば、ベクトル化や自動マルチスレッド)
- 交換ループ
- インライン化は、同等のマクロと同じくらい少なくとも速度でインラインされた方法を作ります
デバッグビルドを使用すると、違いがより明確に表示されます。デバッグビルドを使用すると、最初の実行は場所を表示します。 throw ex
実際の呼び出しに由来するラインと2番目 BadGuy
. 。明らかに「問題」はバドギーへの呼びかけです - スローEXラインではなく、あなたは直接で少ないゴーストを追いかける throw;
声明。
スタックトレースでは、この浅いトレースでは、利点はすぐに明白ではありません。非常に深いスタックでは、問題の実際のソースをマスクし、組み込みの再スローステートメントを使用する代わりに手動でスローすることで忠実度を失います。
サイドノートでは、レスロウのコールスタックを保持できるようにする(その後、参照を失った)ブログに投稿されたハックを見つけました。これは、1つのコンテキスト(たとえば、非同期操作を実行しているスレッド)で例外をキャッチし、別のコンテキスト(非同期操作を起動した他のスレッドなど)でrethrowでreりたい場合に有用です。リモート境界全体にスタックトレースを保存できるように、いくつかの文書化されていない機能を使用します。
//This terrible hack makes sure track trace is preserved if exception is re-thrown
internal static Exception AppendStackTrace(Exception ex)
{
//Fool CLR into appending stack trace information when the exception is re-thrown
var remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString",
BindingFlags.Instance |
BindingFlags.NonPublic);
if (remoteStackTraceString != null)
remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine);
return ex;
}