在记录异常之后,我需要重新引发在执行异步块时发生的异常。

当我执行以下操作时,编译器认为我没有从处理程序中调用reraise函数。我究竟做错了什么?

let executeAsync context = async {
    traceContext.Properties.Add("CorrelationId", context.CorrelationId)
    try
        do! runAsync context
        return None
    with
        | e when isCriticalException(e) ->
            logCriticalException e
            reraise()
        | e ->
            logException e
            return Some(e)
}

最佳答案

粗糙的!我认为这是不可能的,因为重新引发对应于一条特殊的IL指令,该指令从堆栈的顶部捕获异常,但是异步表达式被编译为一系列连续的方式,我认为语义不成立!

出于相同的原因,以下内容也不会编译:

try
    (null:string).ToString()
with e ->
    (fun () -> reraise())()

在这些情况下,我需要在实际的with主体之外处理异常,并且想要模拟reraise(即,保留异常的堆栈跟踪),我使用this解决方案,因此所有代码都看起来像:
let inline reraisePreserveStackTrace (e:Exception) =
    let remoteStackTraceString = typeof<exn>.GetField("_remoteStackTraceString", BindingFlags.Instance ||| BindingFlags.NonPublic);
    remoteStackTraceString.SetValue(e, e.StackTrace + Environment.NewLine);
    raise e

let executeAsync context = async {
    traceContext.Properties.Add("CorrelationId", context.CorrelationId)
    try
        do! runAsync context
        return None
    with
        | e when isCriticalException(e) ->
            logCriticalException e
            reraisePreserveStackTrace e
        | e ->
            logException e
            return Some(e)
}

更新: .NET 4.5引入了ExceptionDispatchInfo,它可以使上面的reraisePreserveStackTrace的实现更干净。

09-11 23:55