Правильный способ подготовки данных в асинхронном отменяемом рабочем процессе с отзывчивым пользовательским интерфейсом - PullRequest
1 голос
/ 08 марта 2012

Этот вопрос основан на Async.TryCancelled не работает с Async.RunSynchronously , который выглядит сложным, поэтому я выделю простую часть, которую я пытаюсь решить.

Предположим, у меня есть следующие функции:

let prepareModel () = 
    async {
        // this might take a lot of time (1-50seconds)
        let! a = ...
        let! b = ...
        let! res = combine a b
        return res
    }
 let updateUI model =
    runOnUIThread model

prepareModel подготавливает данные, которые должны отображаться пользователю. updateUI обновляет пользовательский интерфейс (удаляет старые элементы управления и создает новые ctls на основе новых данных).

Вопрос: Как мне вызвать две функции, чтобы prepareModel можно было отменить в любое время?

Поток

  • пользователь нажимает обновить
    • prepareModel (1) запущен и работает асинхронно, поэтому пользовательский интерфейс отзывчив и пользователь может работать с приложением
  • пользователь изменяет данные и нажимает кнопку обновить снова
    • prepareModel (1) от отменяется и новый prepareModel (2) запущен
  • пользователь изменяет данные и снова нажимает кнопку Обновить
    • prepareModel (2) отменяется и новый prepareModel (3) запущен
    • ..
    • prepareModel (n) закончено
    • updateUI запускается в потоке пользовательского интерфейса, перерисовывает пользовательский интерфейс

(Мое первое решение основано на MailboxProcessor, которое обеспечивает выполнение только одного prepareModel, см. Async.TryCancelled не работает с Async.RunSynchronously , но, как я экспериментировал с этим, это не без ошибок)

1 Ответ

5 голосов
/ 08 марта 2012

Один из возможных подходов - запускать рабочий процесс асинхронно в фоновом режиме, используя Async.Start (тогда его следует отменить).Чтобы перерисовать пользовательский интерфейс в конце, вы можете использовать Async.SwitchToContext, чтобы убедиться, что последняя часть рабочего процесса выполняется в пользовательском интерфейсе.Вот эскиз:

// Capture current synchronization context of the UI
// (this should run on the UI thread, i.e. when starting)
let syncContext = System.Threading.SynchronizationContext.Current

// Cancellation token source that is used for cancelling the
// currently running workflow (this can be mutable)
let cts = ref (new CancellationTokenSource())

// Workflow that does some calculations and then updates gui
let updateModel () =   
    async {  
        // this might take a lot of time (1-50seconds)  
        let! a = ...  
        let! b = ...  
        let! res = combine a b 

        // switch to the GUI thread and update UI
        do! Async.SwitchToContext(syncContext)
        updateUserInterface res
    }  

// This would be called in the click handler - cancel the previous
// computation, creat new cancellation token & start the new one
cts.Cancel()
cts := new CancellationTokenSource()
Async.Start(updateModel(), cts.Token)
...