Async.Catch不适用于OperationCanceledException

时间:2013-08-16 13:53:11

标签: exception-handling f# async-workflow

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

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

今天我注意到Async.Catch不处理OperationCanceledExceptions。而不是从Async.Catch获得选择,异常不断冒泡直到它击中我。我希望下面的测试是红色的,但它是绿色的:

  [<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

处理异步工作流异常的最佳方法是什么?我应该只是转储Async.Catch并在任何地方使用try / catch块吗?

1 个答案:

答案 0 :(得分:6)

取消是异步计算中的一种特殊异常。取消工作流时,这也会取消所有子计算(共享取消令牌)。因此,如果您可以将取消作为一个普通的例外处理,它仍然可以取消计算的其他部分(并且很难推断出正在发生的事情)。

但是,您可以编写启动工作流的原语(并将其与父工作流分开),然后在此子工作流中处理取消。

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(因为工作流程是单独启动的):< / p>

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"