【问题标题】:Handling process corrupted state exceptions using Async.Catch使用 Async.Catch 处理进程损坏状态异常
【发布时间】:2011-09-01 06:52:32
【问题描述】:

我需要在我的应用程序中了解动态执行的底层组件何时引发进程损坏状态异常,以便我可以记录它,并将组件标记为无法再次加载并使我的进程崩溃。

此组件的执行是异步执行的,我正在使用 Async.Catch 来处理该异常。我尝试了以下代码来测试 Async.Catch 的行为,在我看来 Async.Catch 已挂起。这对我来说是一种不良影响,我怀疑所有 PCSE 都会导致相同的行为。

有人知道如何摆脱这种情况吗?

let a = async {
    System.Threading.Thread.CurrentThread.Abort()
}

let b = async {
    let! m = Async.Catch a
    return match m with
            | Choice1Of2 p -> "hello"
            | Choice2Of2 e -> "Caught: " + e.ToString()
}

Async.RunSynchronously b;;

编辑 1:我找到了指向我使用 HandleProcessCorruptedStateExceptionsAttributeSecurityCriticalAttribute 或使用配置条目 legacyCorruptedState­­ExceptionsPolicy=true 的文档。如果可能的话,我不想使用配置条目。

编辑 2:根据评论中的建议,我修改了 'b' 的 let 绑定如下:

let b = async {
        try
            let! m = Async.Catch a
            return "hello"
        with
            | :? System.Threading.ThreadAbortException as e -> return "Caught: " + e.ToString()
    }

程序仍然挂起,不返回也不抛出。

【问题讨论】:

  • 不是您问题的答案,但我怀疑您可以使用 try/with 而不是 Async.Catch 来使您的异步块更具可读性。
  • 修改了代码(见EDIT2),还是一样的行为
  • 我不希望它解决问题,但我的意思是让你做let! m = a return "hello",而不是将Async.Catch 移动到try 块中。
  • 避免拨打Thread.Abort,过上更长寿、更幸福的生活。 stackoverflow.com/questions/421389/…

标签: exception-handling asynchronous f#


【解决方案1】:

这是一个棘手的问题 - 在普通函数中,您可以捕获 ThreadAbortException 并做出反应,但您无法真正处理它,因为它会自动重新抛出(最终会杀死线程)。

在 F# 异步工作流中,异常被处理并且 F# 异步运行时存储它以便它可以通过延续来报告它,但是在它有机会这样做之前,异常被 .NET 重新抛出并杀死线程(因此RunSynchronously 挂起)。

问题是 - 要报告异常,F# async 需要进行一些调用。无法在当前线程(正在取消)上进行调用。如果您期待异常,您可以在线程池中开始工作并自己处理。 (F# 无法自动执行此操作,因为开销太大)。

您可以使用以下助手:

type Microsoft.FSharp.Control.Async with
  static member CatchAbort<'R>(f : unit -> 'R) : Async<'R> =
    async { let hndl = new AutoResetEvent(false)
            let result = ref (Choice3Of3())
            ThreadPool.QueueUserWorkItem(fun _ ->
              try 
                result := Choice1Of3 (f())
                hndl.Set() |> ignore
              with 
                | e -> 
                   // This handler runs correctly even for ThreadAbort
                   result := Choice2Of3 e 
                   hndl.Set() |> ignore) |> ignore
            let! _ = Async.AwaitWaitHandle(hndl) 
            match !result with
            | Choice1Of3(res) -> return res
            | Choice2Of3(exn) -> 
                // Wrap or rethrow the exception in some way
                return raise exn
            | Choice3Of3 _ -> return failwith "unexpected case" }

这会在线程池线程上启动指定的函数(不是异步的)。函数完成或抛出后,将结果返回给原线程,原线程可以恢复工作流。

为了调整您的示例,这应该符合预期:

let a = async {
    let! value = Async.CatchAbort(fun () ->
      System.Threading.Thread.CurrentThread.Abort()
      "not aborted")
    return value }

let b = async {
    try let! m = a
        printfn "ok"
    with e -> printfn "Caught: %A" e }

【讨论】:

  • 感谢您的精彩回答。我的主要要求是处理异步函数内部发生的所有进程损坏状态异常。因此,由于您在回答中提到的原因,我仍然会遇到异步工作流未报告回调中发生的异常的问题?顺便说一句,我仅将线程中止用作示例。现在,看来我可能必须从挂起的情况中恢复的唯一办法是设置一个计时器并报告我的异步工作流因未知原因挂起,然后使进程崩溃。公平吗?
  • 我不确定其他 PCSE 异常的行为如何,但我假设它们以类似的方式终止线程,所以它不应该有所作为。您提到您正在调用外部组件 - 我假设该组件不是用 F# 编写的并且不使用异步工作流,因此您应该能够使用 CatchAbort 将其包装在异步中,就像在我的示例中一样。
  • 如果没有这个额外的包装,您将无法捕捉到异常,因为 F# 异步工作流会尝试在内部捕捉它然后报告它(如果线程终止,这是不可能的)。
  • 我感谢你的“return raise exn”.. 总是在 async-wf 中与 raise 发生争吵,然后做了 return ... 之后,从来没有意识到你这样做了
  • 我认为我不清楚如何使用 Async.Catch 包装对外部组件的调用。我的外部组件是异步的,并且遵循 Begin/End 模式。我正在使用 Async.FromBeginEnd 构建一个异步工作流,因此我可以在我的 F# 代码中无缝地使用它。你能澄清一下你的意思吗?我不想同步调用这个 API,即使它在不同的线程中。
【解决方案2】:

如您所见here - ThreadAbortException 是 CLR 中的特殊异常之一。它似乎很难打破 Asycn-Pattern,所以我想这就是问题所在。

尝试另一个例外,看看是否可行(应该)。

【讨论】:

  • 文档指出 finally 块将在线程终止之前运行,我希望异步工作流能够优雅地处理这个问题。此外,困扰我的是该过程甚至不会崩溃,而是会挂起。我怀疑由于此异常,从未调用过回调。但这不会是异步工作流程中的错误吗?
  • 这或(如文档提示)第一个 TAE 被转换为不同的东西,通用运行时以另一种方式关闭线程而不重新抛出它。毕竟异步中的所有 try/catch(with) 与 normal try/catch 不同,因为您可以在文档中阅读到计算工作流。但我肯定不是专家,但我怀疑这是你的问题。我在其他情况下也有类似的经历——TAE、StackOverflow 等异常总是一个问题,在这些情况下关闭应用程序通常会更好。到现在为止,我总能找到一个无需 Abort 即可工作的重新设计
猜你喜欢
  • 2017-02-18
  • 2012-02-16
  • 1970-01-01
  • 2023-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-31
相关资源
最近更新 更多