Правильный способ подготовки данных в асинхронном отменяемом рабочем процессе с отзывчивым пользовательским интерфейсом
Этот вопрос основан на том, что 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
(п) закончилupdateUI
запускается в потоке пользовательского интерфейса, перерисовывает пользовательский интерфейс
(Мое первое решение основано на MailboxProcessor
это гарантирует, что только один prepareModel
выполняется в Async.TryCancelled не работает с Async.RunSynchronously, но, как я экспериментировал с этим, он не свободен от ошибок)
1 ответ
Одним из возможных подходов было бы запустить рабочий процесс асинхронно в фоновом режиме, используя 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)