我需要在记录异常后重新执行异步块时发生的异常。
当我执行以下操作时,编译器认为我没有在处理程序中调用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)
}
答案 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,因为我们现在正在调用另一个线程,所以我们将异常包装在另一个内部