Бросок против Ретроу: тот же результат? - PullRequest
10 голосов
/ 24 августа 2010

ссылаясь на большое количество документации в сети, особенно на SO, например: Как правильно перебрасывать исключение в C #? должна быть разница между «throw e;»и "throw;".

Но, с: 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

SUMUP : все ваши ответы были великолепны,Еще раз спасибо.

Таким образом, причина заключается в том, что из-за 64-битного JITter причина заключается в эффективном использовании.

Мне пришлось выбрать только один ответ, и вот почему я выбрал LukeH ответ:

  • он угадал проблему со встроением и тот факт, что она может быть связана с моей 64-битной архитектурой,

  • он предоставил NoInliningфлаг, который является самым простым способом избежать этого поведения.

Однако теперь эта проблема поднимает другой вопрос: соответствует ли это поведениесо всеми .Net спецификациями: CLR и C # на языке программирования?

ОБНОВЛЕНИЕ : эта оптимизация кажется совместимой в соответствии с: Throw VS rethrow: тот же результат? (спасибо 0xA3 )

Заранее спасибо за помощь.

Ответы [ 5 ]

4 голосов
/ 24 августа 2010

Я не могу воспроизвести проблему - использование .NET 3.5 (32-разрядная версия) дает мне те же результаты, которые описаны в статье Барта.

Я предполагаю, что компилятор / jitter .NET 4 -или, может быть, это 64-битный компилятор / джиттер, если это происходит и под 3.5 - это включение метода BadGuy в вызывающие методы.Попробуйте добавить следующий атрибут MethodImpl к BadGuy и посмотрите, имеет ли это какое-либо значение:

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void BadGuy()
{
    //
    // Some nasty behavior.
    //
    throw new Exception();
}
3 голосов
/ 24 августа 2010

Я попытался запустить этот код сам, и отладочная сборка работает, как я и ожидал, но я получил тот же результат, что и вы в сборке выпуска.

Подозреваю, что встраивание компилятора просто заменило вызов BadGuy () на throw new Exception();, потому что это единственный оператор в BadGuy ().

Если вы отключите опцию «Оптимизировать код» в свойствах проекта -> экран «Сборка», то сборка «Выпуск» и «Отладка» выдаст один и тот же результат, который показывает BadGuy () вверху трассировки стека.

3 голосов
/ 24 августа 2010

Кажется, что JIT-оптимизаторы здесь работают.Как вы можете видеть, стек вызовов во втором случае отличается от первого случая, когда вы запускаете сборку Debug.Однако в версии Release оба стека вызовов идентичны из-за оптимизации.

Чтобы увидеть, что это связано с джиттером, вы можете украсить методы с атрибутом 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 гарантировал, что исключения были точными , что означает, что состояние программы сохранялось при возникновении исключения. Тем не менее, применение точных исключений для неявных проверок делает некоторые важные оптимизации практически невозможными для применения. Программисты теперь могут объявлять через специальный атрибут, что метод «расслаблен», что говорит об исключениях, возникающих из неявныхвремя проверки не должно быть точным.

Расслабленные проверки сохраняют проверяемость (сохраняя память и безопасность типов), в то же время позволяя оптимизировать порядок команд.В частности, он включает следующие оптимизации:

  • Подъем неявных проверок во время выполнения из циклов.
  • Переупорядочение итераций цикла (например, векторизация и автоматическая многопоточность)
  • Чередование циклов
  • Встраивание, которое делает встроенный метод менее быстрым, чем эквивалентный макрос
1 голос
/ 24 августа 2010

Используйте отладочную сборку, и вы увидите разницу более четко. При отладочной сборке первый запуск покажет местоположение в виде строки throw ex, а второй - как исходный от фактического вызова BadGuy. Очевидно, что «проблема» - это вызов BadGuy, а не бросок ex line, и вы будете преследовать меньше призраков с помощью прямого оператора throw;.

В трассировке стека это мелкое преимущество не столь очевидно, в очень глубоком стеке вы замаскируете реальный источник проблемы и потеряете верность, вручную создавая исключение вместо использования встроенного повторного выброса заявление.

0 голосов
/ 24 августа 2010

Кстати, я однажды обнаружил в блоге взломанный хак (с тех пор я потерял ссылку), который позволяет сохранить стек вызовов при повторном отбрасывании. Это в основном полезно, если вы перехватываете исключение в одном контексте (скажем, в потоке, выполняющем асинхронную операцию) и хотите перебросить его в другом (скажем, в другом потоке, который запустил асинхронную операцию). Он использует некоторые недокументированные функциональные возможности, которые позволяют сохранять следы стека через границы удаленного взаимодействия.

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