Async.Catch not working with OperationCanceledExceptions

I use Async.Catch to handle exceptions thrown by asynchronous workflows:

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

Today I noticed that OperationCanceledExceptions are not handled by Async.Catch. Instead of picking from Async.Catch, the exception continues to be thrown until it hits me. I expected the following test to be red, but it is green:

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

Evaluating some exceptions using Async.Catch + Choices + matching, and some others using try / catch blocks do not look like this ... it will look like this, which is too complicated. Also, I am wondering what to use Async.Catch, since I should still use the try / catch block ...:

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

What is the best way to handle async workflow exceptions? Should I just reset Async.Catch and use try / catch blocks everywhere?

+6
exception-handling f # async-workflow
source share
1 answer

Cancel is a special kind of exception in asynchronous computing. When a workflow is canceled, it also cancels all child calculations (the cancel token is shared). So, if you can handle cancellation as a regular exception, it can still undo some other parts of your calculation (and it would be difficult to reason about what is going on).

However, you can write a primitive that starts the workflow (and separates it from the parent workflow) and then handles the cancellation in this sub-workflow.

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

Usage is similar to Async.Catch , but you should pass the cancellation token to StartCatchCancellation , and not pass it to the main RunSynchronously (since the workflow starts separately):

 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" 
+6
source share

All Articles