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

时间:2011-08-23 23:38:35

标签: asynchronous exception-handling f# f#-async

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

当我执行以下操作时,编译器认为我没有在处理程序中调用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)
}

2 个答案:

答案 0 :(得分:13)

粗糙!我认为这是不可能的,因为重新加载对应于从堆栈顶部抓取异常的特殊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

答案 1 :(得分:3)

我在不同的背景下遇到了类似的问题,但归结为此。

异常不能抛到另一个线程上 - 调用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)

注意,我们仍然无法使用reraise,因为我们现在正在调用另一个线程,所以我们将异常包装在另一个内部