19

重复:在 C# 中,如何在不丢失堆栈跟踪的情况下重新抛出 InnerException?

我有一些在后台线程上异步调用的操作。有时,事情会变糟。发生这种情况时,我往往会得到一个 TargetInvocationException,虽然合适,但它却毫无用处。我真正需要的是 TargetInvocationException 的 InnerException,如下所示:

    try
    {
        ReturnValue = myFunctionCall.Invoke(Target, Parameters);
    }
    catch (TargetInvocationException err)
    {
        throw err.InnerException;
    }

这样,我的调用者就会收到发生的 REAL 异常。问题是,throw 语句似乎重置了堆栈跟踪。我想基本上重新抛出内部异常,但保留它原来的堆栈跟踪。我怎么做?

澄清: 我只想要内部异常的原因是这个类试图“抽象掉”这些函数(由调用者提供的委托)在其他线程上运行的整个事实等等。如果有异常,那么很可能它与在后台线程上运行无关,调用者真的希望堆栈跟踪进入他们的委托并找到真正的问题,而不是我的调用调用。

4

8 回答 8

29

可以重新抛出之前保留堆栈跟踪而不进行反射:

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 ;
}
于 2010-01-18T10:39:59.790 回答
6

不,那是不可能的。您唯一真正的机会是遵循推荐的模式并使用适当的InnerException.

编辑

如果您担心的是存在TargetInvocationException并且您想忽略它(不是我建议这样做,因为它可能与它在另一个线程上运行的事实有关)那么没有什么能阻止您抛出自己的此处例外,并将InnerExceptionfrom附加TargetInvocationException为您自己的InnerException. 它有点臭,但它可能会完成你想要的。

于 2009-06-17T22:08:32.293 回答
5

有一种方法可以通过使用用于在使用远程处理时保留服务器端堆栈跟踪的内部机制来“重置”异常堆栈跟踪,但这很可怕:

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异常字段中,当重新抛出异常时,它将连接到新重置的堆栈跟踪。

这确实是一个可怕的 hack,但它确实实现了你想要的。不过,您正在类内部进行修补,System.Exception因此此方法可能因此在框架的后续版本中中断。

于 2009-06-17T22:44:12.207 回答
2

尽管您可能会觉得 TargetInvocationException 是“无用的”,但这就是现实。不要试图假装 .NET 没有接受原始异常并用 TargetInvocationException 包装它并抛出它。那真的发生了。有一天,您甚至可能想要来自该包装的一些信息——比如可能引发 TargetInvocationException 的代码的位置。

于 2009-06-17T22:11:26.140 回答
0

你不能那样做。throw除非不带参数使用,否则总是重置堆栈跟踪。恐怕您的来电者将不得不使用 InnerException ...

于 2009-06-17T22:10:33.460 回答
0

使用带有异常的“throw”关键字将始终重置堆栈跟踪。

最好的办法是捕捉你想要的实际异常,并使用“throw;” 而不是“扔前;”。或者使用您想要传递的 InnerException 抛出您自己的异常。

我不相信你想做的事情是可能的。

于 2009-06-17T22:10:58.123 回答
0

正如其他人所说,使用“throw”关键字而不添加它以保持异常链完整。如果您需要原始异常(假设这就是您的意思),那么您可以在链的末尾调用 Exception.GetBaseException() 来获取启动它的异常。

于 2009-06-18T13:33:01.807 回答
0

.net 4.5 可以:

catch(Exception e)
{
   ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}
于 2017-06-07T07:29:22.313 回答