使用Async.Catch无法处理OperationCanceledExceptions异常。

8

我使用Async.Catch来处理异步工作流抛出的异常:

work
|> Async.Catch
|> Async.RunSynchronously
|> fun x -> match x with
            | Choice1Of2 _ -> () // success
            | Choice2Of2 ex -> // failure, handle exception

今天我注意到Async.Catch不处理OperationCanceledExceptions。与其从Async.Catch获取Choice,异常会一直向上冒泡,直到它遇到我。我期望以下测试结果为红色,但实际为绿色:

  [<Test>]
  let ``Async.Catch doesnt work on OperationCancelledExceptions``() =
    use cancellationTokenSource = new System.Threading.CancellationTokenSource(1000)

    let work = async {
      while true do
        do! Async.Sleep 100
    }

    (fun () -> work
               |> Async.Catch
               |> fun x -> Async.RunSynchronously (x, cancellationToken=cancellationTokenSource.Token)
               |> ignore)
    |> should throw typeof<System.OperationCanceledException>

使用Async.Catch + Choices + 匹配来评估某些异常,使用try/catch块来处理其他异常似乎不太合适...以下是示例代码,这显然过于复杂。此外,我在想Async.Catch有什么用处,因为我无论如何都必须使用try/catch块...

  [<Test>]
  let ``evaluating exceptions of async workflows``() =
    use cancellationTokenSource = new System.Threading.CancellationTokenSource(1000)

    let work = async {
      while true do
        do! Async.Sleep 100
    }

    try
      work
      |> Async.Catch
      |> fun x -> Async.RunSynchronously (x, cancellationToken=cancellationTokenSource.Token)
      |> fun x -> match x with
                  | Choice1Of2 result -> () // success, process result
                  | Choice2Of2 ex -> () // failure, handle exception
    with ex -> () // another failure, handle exception here too

如何处理异步工作流程的异常?我应该只使用try/catch块,而不是使用Async.Catch吗?

1个回答

8

取消是异步计算中一种特殊的异常。当工作流被取消时,所有子计算也会被取消(取消令牌是共享的)。因此,如果将取消处理为普通异常,它仍可能取消您的计算的某些其他部分(并且很难理解正在发生什么)。

然而,您可以编写一个原始操作,启动工作流(并将其与父工作流分开),然后在此子工作流中处理取消。

type Async = 
  static member StartCatchCancellation(work, ?cancellationToken) = 
    Async.FromContinuations(fun (cont, econt, _) ->
      // When the child is cancelled, report OperationCancelled
      // as an ordinary exception to "error continuation" rather
      // than using "cancellation continuation"
      let ccont e = econt e
      // Start the workflow using a provided cancellation token
      Async.StartWithContinuations( work, cont, econt, ccont, 
                                    ?cancellationToken=cancellationToken) )

使用方法类似于 Async.Catch,但是您需要将取消标记传递给 StartCatchCancellation 而不是传递给主要的 RunSynchronously(因为工作流是单独启动的):

let work = 
  async { while true do
            do! Async.Sleep 100 }

let ct = new System.Threading.CancellationTokenSource(10000)
Async.StartCatchCancellation(work, ct.Token) 
|> Async.Catch
|> Async.RunSynchronously 
|> printfn "%A"

谢谢您的解释,很有道理。您还回答了我另一个问题:“当工作流被取消时,这也会取消所有子计算。”我本来要问这个问题,因为MSDN文档没有说明当父级被取消时子计算会发生什么。 - stmax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接