Как я могу перебросить внутреннее исключение, сохраняя при этом трассировку стека?
Дубликат: В С#, как я могу перекрыть InnerException без потери трассировки стека?
У меня есть некоторые операции, которые я вызываю асинхронно в фоновом потоке. Иногда все идет плохо. Когда это происходит, я, как правило, получаю TargetInvocationException, которое, при необходимости, совершенно бесполезно. Я действительно нуждаюсь в TargetInvocationException InnerException, например:
try
{
ReturnValue = myFunctionCall.Invoke(Target, Parameters);
}
catch (TargetInvocationException err)
{
throw err.InnerException;
}
Таким образом, мои абоненты обслуживаются с исключением REAL, которое произошло. Проблема заключается в том, что выражение throw выглядит как reset трассировка стека. Я бы хотел, в основном, восстановить внутреннее исключение, но сохранить трассировку стека изначально. Как это сделать?
УТОЧНЕНИЕ:
Причина, по которой я хочу только внутреннее исключение, заключается в том, что этот класс пытается "абстрагировать" весь факт, что эти функции (делегаты, предоставленные вызывающим) выполняются на других потоках и еще много чего. Если есть исключение, то вероятность того, что это не имеет никакого отношения к запуску в фоновом потоке, и вызывающему пользователю действительно понравится трассировка стека, которая входит в их делегат, и находит реальную проблему, а не мой вызов для вызова.
Ответы
Ответ 1
можно сохранить трассировку стека, прежде чем реконструировать без отражения:
static void PreserveStackTrace (Exception e)
{
var ctx = new StreamingContext (StreamingContextStates.CrossAppDomain) ;
var mgr = new ObjectManager (null, ctx) ;
var si = new SerializationInfo (e.GetType (), new FormatterConverter ()) ;
e.GetObjectData (si, ctx) ;
mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData
mgr.DoFixups () ; // ObjectManager calls SetObjectData
// voila, e is unmodified save for _remoteStackTraceString
}
Это отнимает много циклов по сравнению с InternalPreserveStackTrace, но имеет преимущество, полагаясь только на общедоступные функции. Вот несколько общих шаблонов использования для функций сохранения стека:
// usage (A): cross-thread invoke, messaging, custom task schedulers etc.
catch (Exception e)
{
PreserveStackTrace (e) ;
// store exception to be re-thrown later,
// possibly in a different thread
operationResult.Exception = e ;
}
// usage (B): after calling MethodInfo.Invoke() and the like
catch (TargetInvocationException tiex)
{
PreserveStackTrace (tiex.InnerException) ;
// unwrap TargetInvocationException, so that typed catch clauses
// in library/3rd-party code can work correctly;
// new stack trace is appended to existing one
throw tiex.InnerException ;
}
Ответ 2
Нет, это невозможно. Ваша единственная реальная возможность - следовать рекомендуемому шаблону и бросить свое собственное исключение с соответствующим InnerException
.
Edit
Если ваша проблема связана с присутствием TargetInvocationException
, и вы хотите ее игнорировать (не то, что я рекомендую это, так как это может очень хорошо иметь отношение к тому, что оно выполняется в другом потоке), тогда ничего чтобы вы не выбрасывали свое собственное исключение и прикрепляли InnerException
к TargetInvocationException
как свой собственный InnerException
. Это немного вонючий, но он может выполнить то, что вы хотите.
Ответ 3
Существует способ "сбросить" трассировку стека в исключении, используя внутренний механизм, который используется для сохранения трассировки стека на стороне сервера при использовании удаленных операций, но это ужасно:
try
{
// some code that throws an exception...
}
catch (Exception exception)
{
FieldInfo remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
remoteStackTraceString.SetValue(exception, exception.StackTrace);
throw exception;
}
Это ставит исходную трассировку стека в поле _remoteStackTraceString
исключения, которое объединяется в новую трассировку стека reset при повторном выпуске исключения.
Это действительно ужасный хак, но он действительно достигает того, чего вы хотите. Вы возитесь внутри класса System.Exception
, хотя таким образом этот метод может поэтому нарушить последующие версии фреймворка.
Ответ 4
Хотя вы можете почувствовать, что TargetInvocationException "бесполезно", это реальность. Не пытайтесь притвориться, что .NET не взял оригинальное исключение и обернул его с помощью исключения TargetInvocationException и выбросил его. Это действительно так. В какой-то день вам может понадобиться какая-то часть информации, которая поступает из этой упаковки - например, может быть, местоположение кода, в котором было выбрано TargetInvocationException.
Ответ 5
Вы не можете этого сделать. throw
всегда сбрасывает трассировку стека, если не используется без параметра. Я боюсь, что вашим абонентам придется использовать InnerException...
Ответ 6
Использование ключевого слова "throw" с исключением всегда будет reset трассировкой стека.
Лучше всего поймать фактическое исключение, которое вы хотите, и использовать "throw"; вместо "throw ex". Или бросить свое собственное исключение, с InnerException, которое вы хотите передать.
Я не верю, что вы хотите сделать.
Ответ 7
Как говорили другие, используйте ключевое слово "throw", не добавляя к нему, чтобы сохранить цепочку исключений неповрежденной. Если вам нужно это оригинальное исключение (предполагая, что это то, что вы имеете в виду), вы можете вызвать Exception.GetBaseException() в конце своей цепочки, чтобы получить Исключение, которое запустило все это.
Ответ 8
Возможно с .net 4.5:
catch(Exception e)
{
ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}