Async.TryCancelled не работает с Async.RunSynchronously

Я пытаюсь создать агент, который обновляет пользовательский интерфейс на основе взаимодействия с пользователем. Если пользователь нажимает на кнопку, графический интерфейс должен быть обновлен. Подготовка модели занимает много времени, поэтому желательно, чтобы, если пользователь нажимает на другую кнопку, подготовка отменяется и запускается новая.

Что у меня так далеко:

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

Я возвращаюCancellationTokenSource для каждого запроса и сохранения его в изменяемой переменной (x.Refresh () является потокобезопасным, он вызывается в потоке пользовательского интерфейса). Если Refresh () вызывается впервые, возвращается источник отмены. Если Refresh () вызывается во второй раз, я вызываю Cancel, который должен прервать асинхронную задачу, которую я запускаю через Async.RunSynchronously.

Однако возникает исключение. Выход из моего образца

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)

Теперь, когда я думаю об этом, это может иметь смысл, потому что поток, в котором работает агент, был прерван, верно? Но как мне добиться желаемого поведения?

Мне нужно отменить асинхронный рабочий процесс внутри агента, чтобы агент мог продолжать потреблять новые сообщения. Почему я использую процессор почтовых ящиков? Потому что гарантируется, что только один поток пытается создать модель пользовательского интерфейса, поэтому я экономлю ресурсы.

Давайте предположим, что я создаю модель пользовательского интерфейса, загружая данные из нескольких веб-сервисов, поэтому я использую асинхронный вызов. Когда пользователь меняет комбо и выбирает другую опцию, я хочу прекратить запрашивать веб-сервисы (= отменить асинхронные вызовы) со старым значением и хочу создать новую модель базы вызовов веб-сервисов с новым значением.

Любое предложение, которое я могу использовать вместо своего решения и решит мою проблему, также приветствуется.

Ответы на вопрос(1)

Ваш ответ на вопрос