【问题标题】:Throw VS rethrow : same result?投掷 VS 重投:同样的结果?
【发布时间】:2011-04-02 21:37:25
【问题描述】:

参考网络上的大量文档,特别是关于 SO,例如:What is the proper way to re-throw an exception in C#? "throw 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-without-resetting.html的代码获得相同的结果

原始问题:我做错了什么?

更新:与 .Net 3.5 / csc.exe 3.5.30729.4926 的结果相同

总结:您的所有答案都很棒,再次感谢。

所以原因是由于 64 位 JITter 导致的有效内联。

我只能选择一个答案,这就是我选择 LukeH 答案的原因:

  • 他猜到了内联问题,可能和我的64位架构有关,

  • 他提供了 NoInlining 标志,这是避免这种行为的最简单方法。

然而,这个问题现在引发了另一个问题:这种行为是否符合所有 .Net 规范:CLR 规范和 C# 编程语言规范?

更新:此优化似乎符合:Throw VS rethrow : same result?(感谢 0xA3

提前感谢您的帮助。

【问题讨论】:

    标签: c# exception-handling throw rethrow


    【解决方案1】:

    使用调试版本,您会更清楚地看到差异。使用调试版本,第一次运行将显示位置为throw ex 行,第二次运行将显示来自对BadGuy 的实际调用。显然,“问题”是对 BadGuy 的调用 - 而不是 throw ex 行,直接使用 throw; 语句,您将追逐更少的幽灵。

    在这么浅的堆栈跟踪中,好处并不那么明显,在非常深的堆栈中,您将掩盖问题的实际根源并通过手动抛出异常而不是使用内置的重新抛出来失去一些保真度声明。

    【讨论】:

      【解决方案2】:

      我已尝试自己运行此代码,并且调试版本按预期工作,但我在发布版本中得到了与您相同的结果。

      我怀疑发生的事情是编译器内联简单地将 BadGuy() 调用替换为 throw new Exception();,因为这是 BadGuy() 中的唯一语句。

      如果您在项目属性 -> 构建屏幕中关闭“优化代码”选项,那么发布和调试构建都会产生相同的结果,即在堆栈跟踪顶部显示 BadGuy()。

      【讨论】:

      • 内联似乎是实际发生的事情:很好的猜测!
      【解决方案3】:

      JIT 优化器似乎在这里做了一些工作。如您所见,当您运行 Debug 构建时,第二种情况下的调用堆栈与第一种情况下的不同。但是,在 Release 版本中,由于优化,两个调用堆栈是相同的。

      要查看这与抖动有关,您可以使用MethodImplAttribute 属性装饰方法:

      [MethodImpl(MethodImplOptions.NoOptimization)]
      private static void ThrowWithoutVariable()
      {
          try
          {
              BadGuy();
          }
          catch
          {
              throw;
          }
      }
      

      请注意,ThrowWithoutVariableThrowWithVariable 的 IL 仍然不同:

      .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 编译器启用重要的优化。 Annex F 第 52 页上的状态(我强调):

      一些 CIL 指令执行隐式 运行时检查,确保内存和 类型安全。 最初,CLI 保证例外是 精确,表示程序状态 当异常发生时被保留 抛出。 但是,执行精确 隐式检查的例外情况 一些重要的优化 几乎不可能申请。 程序员现在可以通过 自定义属性,方法是 “relaxed”,表示异常 由隐式运行时检查引起 不需要精确。

      宽松的检查 保持可验证性(通过保持 内存和类型安全)而 允许重新排序的优化 指示。特别是,它 启用以下优化:

      • 提升隐式运行时签出 循环数。
      • 重新排序循环迭代 (例如,矢量化和自动 多线程)
      • 交换循环
      • 内联,使内联 方法至少与 等效宏

      【讨论】:

      • 谢谢,NoOptimization 标志在所有情况下都给出了预期的结果。
      • 再次感谢您对此类优化合规性的丰富回答。
      【解决方案4】:

      我无法重现该问题 - 使用 .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();
      }
      

      【讨论】:

      • 你是对的,使用 NoInlining 标志就可以了。谢谢。
      【解决方案5】:

      顺便说一句,我曾经在博客上发现了一个 hack(我已经失去了参考),它允许您在重新抛出时保留调用堆栈。如果您在一个上下文中捕获异常(例如,在运行异步操作的线程中)并希望在另一个上下文中重新抛出它(例如,在启动异步操作的另一个线程中),这主要是有用的。它利用包含的一些未记录的功能来允许跨远程边界保存堆栈跟踪。

          //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;
          }
      

      【讨论】:

      • 不需要这样做,这就是 InnerException 的用途。 throw new Exception( "I blew up", ex ).
      • 这真是太可怕了,千万不要用。
      • @Paul,InnerException 的问题是catch 子句分解(它们需要捕获外部异常类型)。如果您希望异步处理程序能够以与调用同步版本相同的方式解释异常,这会破坏透明度。有一些方法可以解决这个问题(您也可以让同步版本包装异常),但在捕获方面有点尴尬。
      • @0xA3,我同意这很糟糕,但公平地说,MS 通过让远程处理系统将异常状态作为其魔法的一部分来有效地支持远程处理。
      • 这段代码的最大问题是 任何 更新或 mscorlib 补丁可能会破坏 您的 应用程序。无法保证 System.Exception 有一个像这样命名的私有字段,并且它在所有情况下都按照您期望的方式运行。对于System.Runtime.Remoting,使用这种机制非常好,因为在 mscorlib 中公开了internal 方法来修改远程堆栈跟踪。
      猜你喜欢
      • 1970-01-01
      • 2013-08-07
      • 1970-01-01
      • 2013-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-24
      相关资源
      最近更新 更多