Async.TryCancelled não funciona com Async.RunSynchronously

Tento criar um agente que atualize a interface do usuário com base na interação do usuário. Se o usuário clicar em um botão, a GUI deve ser atualizada. A preparação do modelo leva muito tempo, por isso é desejável que, se o usuário clicar em outro botão, a preparação seja cancelada e a nova iniciad

O que eu tenho até agora:

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

Volto umCancellationTokenSource para cada solicitação e armazene-a em uma variável mutável (o x.Refresh () é seguro para threads, é chamado no thread da interface do usuário). Se Refresh () for chamado pela primeira vez, a fonte de cancelamento será retornada. Se Refresh () for chamado pela segunda vez, chamo Cancel, que deve abortar a tarefa assíncrona executada por Async.RunSynchronousl

No entanto, uma exceção é gerada. A saída da minha amostra é

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)

Agora, como penso sobre isso, pode fazer sentido, porque o encadeamento no qual o agente é executado foi interrputado, certo? Mas como faço para alcançar o comportamento desejado?

Preciso cancelar o fluxo de trabalho assíncrono dentro do agente, para que ele possa continuar consumindo novas mensagens. Por que eu uso o processador de caixa de correio? Porque é garantido que apenas um segmento está tentando criar um modelo de interface do usuário, por isso economizo recurso

Suponhamos que eu crie um modelo de interface do usuário fazendo o download de dados de vários serviços da web, por isso que uso a chamada assíncrona. Quando o usuário altera um combo e seleciona outra opção, desejo interromper a consulta dos serviços da web (= cancelar as chamadas assíncronas) com valor antigo e criar uma nova base de modelo ou chamada de serviços da web com novo valo

Qualquer sugestão que eu possa usar em vez da minha solução e resolver meu problema também é bem-vind

questionAnswers(2)

yourAnswerToTheQuestion