2013-08-16 16 views
6

che uso Async.Catch per gestire le eccezioni generate da flussi di lavoro asincroni:Async.Catch non funziona su OperationCanceledExceptions

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

Oggi ho notato che OperationCanceledExceptions non sono gestite da Async.Catch. Invece di ottenere una scelta da Async.Catch l'eccezione continua a gorgogliare fino a quando non mi colpisce. Mi aspettavo il seguente test per essere rosso, ma è verde:

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

Valutare alcune eccezioni con Async.Catch + Scelte + matching e alcuni altri che utilizzano blocchi try/catch non mi sembra giusto ... sarebbe risultato come il seguente, che è troppo complicato. Oltre a questo mi chiedo che cosa usare Async.Catch ha, dal momento che ho di utilizzare un blocco try/catch in ogni caso ...:

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

Qual è il modo migliore per gestire le eccezioni di flussi di lavoro asincrone? Dovrei semplicemente scaricare Async.Catch e usare try/catch blocks ovunque?

risposta

6

L'annullamento è un tipo speciale di eccezione nei calcoli asincroni. Quando un flusso di lavoro viene annullato, annulla anche tutti i calcoli secondari (il token di cancellazione è condiviso). Quindi, se si potesse gestire l'annullamento come un'eccezione ordinaria, potrebbe comunque cancellare alcune altre parti del calcolo (e sarebbe difficile ragionare su ciò che sta accadendo).

Tuttavia, è possibile scrivere una primitiva che avvia un flusso di lavoro (e lo separa dal flusso di lavoro principale) e quindi gestisce l'annullamento in questo flusso di lavoro secondario.

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

L'utilizzo è simile a Async.Catch, ma si deve passare il token cancellazione di StartCatchCancellation invece di passarlo al principale RunSynchronously (separatamente perché viene avviato il flusso di lavoro):

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" 
+0

Grazie per il spiegazione, ha senso. Hai anche risposto ad un'altra domanda che ho avuto con "Quando un flusso di lavoro viene cancellato, questo annulla anche tutti i calcoli del bambino". Stavo per chiederlo perché i documenti MSDN non dicono cosa succede con i calcoli secondari quando il genitore viene cancellato. – stmax