Forma correcta de cómo preparar los datos en async cancelables flujo de trabajo con la interfaz de usuario sensible
-
09-12-2019 - |
Pregunta
Esta pregunta se basa en Async.TryCancelled no funciona con Async.RunSynchronously que parece complejo, así que voy a cortar una simple parte en la que trato de resolver.
Supongamos que tengo estas funciones:
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
prepara los datos que se deben mostrar al usuario. updateUI
actualiza la interfaz de usuario (elimina los viejos controles y crea nuevas ctl basado en nuevos datos).
Pregunta: ¿Cómo debo llamar a las dos funciones, a fin de que prepareModel
es cancelable en cualquier momento?
El flujo es
- usuario hace clic en actualizar
prepareModel
(1) inicia y se ejecuta de forma asíncrona, por lo que la interfaz de usuario es sensible y el usuario puede trabajar con la aplicación
- el usuario cambia los datos y hace clic en actualizar de nuevo
prepareModel
(1) se cancela y nuevoprepareModel
(2) se inicia
- el usuario cambia los datos y hace clic en actualizar de nuevo
prepareModel
(2) se cancela y nuevoprepareModel
(3) se inicia- ..
prepareModel
(n) terminadoupdateUI
se corrió en el subproceso de interfaz de usuario, actualiza la interfaz de usuario
(Mi primera solución se basa en MailboxProcessor
que garantiza que sólo un prepareModel
es ejecutado, ver en Async.TryCancelled no funciona con Async.RunSynchronously pero como ya he experimentado con esto, no está libre de errores)
Solución
Un posible enfoque sería para iniciar el flujo de trabajo de forma asincrónica utilizando Async.Start
(entonces debe ser cancelable).Para volver a dibujar la interfaz de usuario al final, puede utilizar Async.SwitchToContext
para asegurarse de que la última parte del flujo de trabajo se ejecuta en la interfaz de usuario.Aquí es un boceto:
// 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)