简体   繁体   English

如何在 F# 的异步工作流中使用 reraise?

[英]How to use reraise in async workflows in F#?

I needed to reraise an exception that occurs while executing an async block, after logging the exception.在记录异常后,我需要重新引发在执行异步块时发生的异常。

When I do the following the compiler thinks that I am not calling the reraise function from within the handler.当我执行以下操作时,编译器认为我没有从处理程序中调用 reraise function。 What am I doing wrong?我究竟做错了什么?

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)
}

Rough! 粗! I think this is impossible, because reraise corresponds to a special IL instruction that grabs the exception from the top of the stack, but the way async expressions are compiled into a chain of continuations, I don't think the semantics hold! 我认为这是不可能的,因为重新加载对应于从堆栈顶部获取异常的特殊IL指令,但异步表达式被编译成连续的链的方式,我不认为语义成立!

For the same reason, the following won't compile either: 出于同样的原因,以下内容也不会编译:

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

In these situations, where I need to handle the exception outside of the actual with body, and would like to emulate reraise (that is, preserve the stack trace of the exception), I use this solution, so all together your code would look like: 在这些情况下,我需要处理实际with body之外的异常,并且想模拟reraise (即保留异常的堆栈跟踪),我使用这个解决方案,所以你的代码一起看起来像:

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)
}

Update: .NET 4.5 introduced ExceptionDispatchInfo which may allow a cleaner implementation of reraisePreserveStackTrace above. 更新: .NET 4.5引入了ExceptionDispatchInfo ,它可以允许更清晰地实现上面的reraisePreserveStackTrace

I ran in to a similar problem, in a different context, but it boils down to this. 我在不同的背景下遇到了类似的问题,但归结为此。

Exceptions cannot be thrown onto a different thread - calling reraise() would require an exception handler running in some sense 'above' the original async block in the code. 异常不能被抛到另一个线程上 - 调用reraise()需要一个异常处理程序,它在代码中的原始异步块的“上方”运行。

let runAsync context = async {return ()}
let isCriticalException e = true
let logCriticalException e = ()
let logException e = ()
let executeAsync context = 
    async {
            do! runAsync context
            return None
}

let run = 
    match executeAsync 5 |> Async.Catch |> Async.RunSynchronously with
    |Choice1Of2(t) -> 
        printfn "%A" t
        None
    |Choice2Of2(exn) ->  
            match exn with
            | e when isCriticalException(e) ->
                logCriticalException e
                raise (new System.Exception("See inner exception",e)) //stack trace will be lost at this point if the exn is not wrapped
            | e ->
                logException e
                Some(e)

Note, we still can't use reraise, as we are now calling on a different thread, so we wrap the exception inside another one 注意,我们仍然不能使用reraise,因为我们现在正在调用另一个线程,所以我们将异常包装在另一个中

As mentioned in comments and updates to answers, you can use ExceptionDispatchInfo as a workaround.如评论和答案更新中所述,您可以使用ExceptionDispatchInfo作为解决方法。

open System.Runtime.ExceptionServices

let inline reraiseAnywhere<'a> (e: exn) : 'a =
    ExceptionDispatchInfo.Capture(e).Throw()
    Unchecked.defaultof<'a>

Usage:用法:

async {
    try
         do! someAsyncThing ()
         return "Success"
    with
    | e ->
        if errorIsOk e then return "Handled error"
        else return (reraiseAnyWhere e)
}

Here is a GitHub Gist I made with unit tests to check its behavior.这是我用单元测试制作的 GitHub 要点,以检查其行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM