我需要在记录异常后重新引发执行异步块时发生的异常。
当我执行以下操作时,编译器认为我没有从处理程序内调用重新引发函数。我做错了什么?
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)
}
粗糙!我认为这是不可能的,因为 reraise 对应于一个特殊的 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
。
我在不同的环境中遇到了类似的问题,但归根结底就是这样。
异常不能被抛出到不同的线程上 - 调用
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)
注意,我们仍然无法使用重新引发,因为我们现在正在不同的线程上调用,因此我们将异常包装在另一个线程中
正如评论和答案更新中提到的,您可以使用
ExceptionDispatchInfo
作为解决方法。
open System.Runtime.ExceptionServices
let inline reraiseAnywhere<'a> (e: exn) : 'a =
ExceptionDispatchInfo.Capture(e).Throw()
Unchecked.defaultof<'a>
用途:
async {
try
do! someAsyncThing ()
return "Success"
with
| e ->
if errorIsOk e then return "Handled error"
else return (reraiseAnyWhere e)
}
这是一个 GitHub Gist 我用单元测试来检查其行为。
@JamesFaix'
的答案...
参见https://github.com/fsharp/fslang-suggestions/issues/660
将其放入
Infrastructure.fs
文件中:
namespace global
type Exception with
// https://github.com/fsharp/fslang-suggestions/issues/660
member this.Reraise() =
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(this).Throw()
Unchecked.defaultof<_>
使用示例:
let! client = async {
try return! CosmosStoreClient.Connect(x, databaseId, containers)
with e -> Log.Error(e, "Could not connect to {databaseId}/{containers}", databaseId, containers)
return e.Reraise() }