Async. TryCancelled no funciona con Async.RunSynchronously

Intento crear un agente que actualice la interfaz de usuario en función de la interacción del usuario. Si el usuario hace clic en un botón, la GUI debería actualizarse. La preparación del modelo lleva mucho tiempo, por lo que es deseable que si el usuario hace clic en otro botón, la preparación se cancela y se inicia el nuevo.

Lo que tengo hasta ahora:

open System.Threading
type private RefreshMsg = 
    | RefreshMsg of AsyncReplyChannel<CancellationTokenSource>

type RefresherAgent() =  
    let mutable cancel : CancellationTokenSource = null

    let doSomeModelComputation i =
        async { 
          printfn "start %A" i
          do! Async.Sleep(1000)
          printfn "middle %A" i
          do! Async.Sleep(1000)
          printfn "end %A" i
        }
    let mbox = 
        MailboxProcessor.Start(fun mbx ->
            let rec loop () = async {
                let! msg = mbx.Receive()
                match msg with
                | RefreshMsg(chnl) ->
                    let cancelSrc = new CancellationTokenSource()
                    chnl.Reply(cancelSrc)
                    let update = async {
                                    do! doSomeModelComputation 1
                                    do! doSomeModelComputation 2
                                    //do! updateUI // not important now
                                 }
                    let cupdate = Async.TryCancelled(update, (fun c -> printfn "refresh cancelled"))
                    Async.RunSynchronously(cupdate, -1, cancelSrc.Token)
                    printfn "loop()"
                    return! loop()
            }
            loop ())
    do
        mbox.Error.Add(fun exn -> printfn "Error in refresher: %A" exn)
    member x.Refresh() = 
        if cancel <> null then
            // I don't handle whether the previous computation finished
            // I just cancel it; might be improved
            cancel.Cancel()
            cancel.Dispose()
        cancel <- mbox.PostAndReply(fun reply -> RefreshMsg(reply))
        printfn "x.Refresh end"

//sample  
let agent = RefresherAgent()
agent.Refresh()
System.Threading.Thread.Sleep(1500)
agent.Refresh()

Devuelvo unCancellationTokenSource para cada solicitud y almacenarlo en una variable mutable (el x.Refresh () es seguro para subprocesos, se llama en el subproceso de interfaz de usuario). Si se llama a Refresh () por primera vez, se devuelve la fuente de cancelación. Si se llama a Refresh () por segunda vez, llamo a Cancel que debería cancelar la tarea asincrónica que ejecuto a través de Async.RunSynchronously.

Sin embargo, se plantea una excepción. El resultado de mi muestra es

x.Refresh end
start 1
middle 1
end 1
refresh cancelled
Error in refresher: System.OperationCanceledException: The operation was canceled.
   at Microsoft.FSharp.Control.AsyncBuilderImpl.commit[a](Result`1 res)

Ahora, mientras pienso en esto, podría tener sentido, porque el hilo en el que se ejecuta el agente fue interrputado, ¿verdad? Pero, ¿cómo logro el comportamiento deseado?

Necesito cancelar el flujo de trabajo asíncrono dentro del agente, para que el agente pueda seguir consumiendo nuevos mensajes. ¿Por qué uso el procesador del buzón? Porque está garantizado que solo un subproceso está intentando crear un modelo de interfaz de usuario, por lo que ahorro recursos.

Supongamos que creo un modelo de IU descargando datos de varios servicios web, por eso uso una llamada asincrónica. Cuando el usuario cambia un combo y selecciona otra opción, quiero dejar de consultar los servicios web (= cancelar las llamadas asíncronas) con un valor anterior y quiero crear una nueva base de modelo de llamadas a servicios web con un nuevo valor.

Cualquier sugerencia que pueda usar en lugar de mi solución y resolveré mi problema, también es bienvenida.

Respuestas a la pregunta(2)

Su respuesta a la pregunta