2012-01-27 10 views
7

La siguiente prueba muestra que Async.Sleep en F # 2.0 no se puede cancelar de inmediato. Recibiremos una notificación de 'cancelar' solo después de que haya transcurrido el tiempo.¿Hay alguna razón por la que Async.Sleep no se puede cancelar de inmediato?

module async_sleep_test 
    open System 
    open System.Threading 
    open System.Threading.Tasks 
    open System.Xml 

    let cts = new CancellationTokenSource() 
    Task.Factory.StartNew(fun() -> 
     try 
      Async.RunSynchronously(async{ 
       printfn "going to sleep" 
       do! Async.Sleep(10000) 
      }, -1(*no timeout*), cts.Token) 
      printfn "sleep completed" 
     with 
     | :? OperationCanceledException -> 
      printfn "sleep aborted" // we will see it only after 10 sec. 
     | _ -> 
      printfn "sleep raised error" 
    ) |> ignore 
    Thread.Sleep(100) // give time to the task to enter in sleep 
    cts.Cancel() 
    Thread.Sleep(100) // give chance to the task to complete before print bye message 
    printfn "press any key to exit...." 
    Console.ReadKey(true) |> ignore 

Creo que esto es comportamiento incorrecto. ¿Cómo crees que es esto un error? Estará allí sorpresas si por ejemplo voy a utilizar la siguiente implementación:

static member SleepEx(milliseconds:int) = async{ 
    let disp = new SerialDisposable() 
    use! ch = Async.OnCancel(fun()->disp.Dispose()) 
    do! Async.FromContinuations(fun (success, error, cancel) -> 
     let timerSubscription = new SerialDisposable() 
     let CompleteWith = 
      let completed = ref 0 
      fun cont -> 
       if Interlocked.Exchange(completed, 1) = 0 then 
        timerSubscription.Dispose() 
        try cont() with _->() 

     disp.Disposable <- Disposable.Create(fun()-> 
      CompleteWith (fun()-> cancel(new OperationCanceledException())) 
     ) 
     let tmr = new Timer(
      callback = (fun state -> CompleteWith(success)), 
      state = null, dueTime = milliseconds, period = Timeout.Infinite 
     ) 
     if tmr = null then 
      CompleteWith(fun()->error(new Exception("failed to create timer"))) 
     else 
      timerSubscription.Disposable <- Disposable.Create(fun()-> 
       try tmr.Dispose() with _ ->() 
      ) 
    ) 
} 

Respuesta

2

Creo que se trata de un comportamiento incorrecto. ¿Cómo crees que es esto un error?

Sí, pensé que era un error. Lo reporté como un error. Microsoft estuvo de acuerdo en que era un error. Han solucionado el error en F # 3.0/VS2012 junto con errores en TryScan y otros.

5

Yo no diría que este es un error - se deduce de la forma cancelación se maneja en los flujos de trabajo F # asíncronos en general. Generalmente, F # asume que las operaciones primitivas que llama utilizando let! o do! no admiten la cancelación (supongo que no existe un mecanismo estándar para eso en .NET), por lo que F # inserta comprobaciones de cancelación antes y después de la llamada realizada usando let!.

Así una llamada let! res = foo() es en realidad más como el siguiente (aunque los controles se ocultan en la implementación de la biblioteca de async):

token.ThrowIfCancellationRequested() 
let! res = foo() 
token.ThrowIfCancellationRequested() 

Por supuesto, el flujo de trabajo devuelto por foo() puede manejar la cancelación mejor - por lo general, si se implementa utilizando un bloque async { .. }, contendrá más controles alrededor de cada let!. Sin embargo, en general (a menos que alguna operación se implemente de una manera más inteligente), la cancelación se realizará después de que finalice la siguiente llamada let!.

Su definición alternativa de Sleep se ve muy bien a mí - es compatible con la cancelación mejor que la que está disponible en el # biblioteca F y si es necesario la cancelación inmediata, volviendo a poner F # 's Async.Sleep con su SleepEx es el único camino a seguir. Sin embargo, es probable que aún haya algunas operaciones que no admitan la cancelación inmediata, por lo que puede tener problemas en cualquier otro lugar (si necesita este comportamiento en todas partes).

PD: Creo que su función SleepEx podría ser muy útil para otros. Si pudieras compartirlo en el F# Snippets web site, ¡sería fantástico!

Cuestiones relacionadas