Cha*_*ari 11 f# asynchronous exception-handling 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)
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*sen 13
粗!我认为这是不可能的,因为重新加载对应于从堆栈顶部抓取异常的特殊IL指令,但异步表达式被编译成连续的链的方式,我不认为语义成立!
出于同样的原因,以下内容也不会编译:
try
(null:string).ToString()
with e ->
(fun () -> reraise())()
Run Code Online (Sandbox Code Playgroud)
在这些情况下,我需要在实际with正文之外处理异常,并且想要模拟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)
}
Run Code Online (Sandbox Code Playgroud)
更新: .NET 4.5引入了ExceptionDispatchInfo,可以更清晰地实现reraisePreserveStackTrace上述功能.
正如评论和答案更新中提到的,您可以将其用作ExceptionDispatchInfo解决方法。
open System.Runtime.ExceptionServices
let inline reraiseAnywhere<'a> (e: exn) : 'a =
ExceptionDispatchInfo.Capture(e).Throw()
Unchecked.defaultof<'a>
Run Code Online (Sandbox Code Playgroud)
用法:
async {
try
do! someAsyncThing ()
return "Success"
with
| e ->
if errorIsOk e then return "Handled error"
else return (reraiseAnyWhere e)
}
Run Code Online (Sandbox Code Playgroud)
这是我用单元测试创建的 GitHub Gist,以检查其行为。
我在不同的背景下遇到了类似的问题,但归结为这一点。
异常不能被抛出到不同的线程上 - 调用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)
Run Code Online (Sandbox Code Playgroud)
请注意,我们仍然无法使用重新引发,因为我们现在正在不同的线程上调用,因此我们将异常包装在另一个线程中