[英]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.