From 005df3a3be28c32a61dcafeb4504a719acbd9f73 Mon Sep 17 00:00:00 2001 From: Joshua Marner Date: Fri, 1 Sep 2023 16:24:18 -0500 Subject: [PATCH 1/3] Ensure that latest model is always applied --- src/Elmish.WPF/WpfProgram.fs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Elmish.WPF/WpfProgram.fs b/src/Elmish.WPF/WpfProgram.fs index 8231fa15..73b87034 100644 --- a/src/Elmish.WPF/WpfProgram.fs +++ b/src/Elmish.WPF/WpfProgram.fs @@ -172,6 +172,7 @@ module WpfProgram = // Core Elmish calls this from `dispatch`, which means this is always called from `elmishDispatcher` // (which is UI thread in single-threaded case) + let mutable pendingModel = ValueNone let setUiState model _syncDispatch = match viewModel with | None -> // no view model yet, so create one @@ -189,11 +190,23 @@ module WpfProgram = | Some vm -> // view model exists, so update match threader with | Threaded_UIDispatch uiWaiter -> // We are in the specific dispatch call from the UI thread (see `synchronizedUiDispatch` in `dispatchFromViewModel`) - uiWaiter.SetResult(fun () -> program.UpdateViewModel (vm, model)) // execute `UpdateViewModel` on UI thread + uiWaiter.SetResult(fun () -> program.UpdateViewModel (vm, model); pendingModel <- ValueNone) // execute `UpdateViewModel` on UI thread | Threaded_PendingUIDispatch _ -> // We are in a non-UI dispatch that updated the model before the UI got its update in, but after the user interacted () // Skip updating the UI since the screen is frozen anyways, and `program.UpdateViewModel` is fully transitive | Threaded_NoUIDispatch -> // We are in a non-UI dispatch with no pending user interactions known - element.Dispatcher.InvokeAsync(fun () -> program.UpdateViewModel (vm, model)) |> ignore // Schedule update normally + let scheduleJob () = + pendingModel <- ValueSome model + + let executeJob () = + match pendingModel with + | ValueSome m -> + program.UpdateViewModel (vm, m) + pendingModel <- ValueNone + | ValueNone -> + bindingsLogger.LogDebug("Job was empty - No update done.") + + element.Dispatcher.InvokeAsync(scheduleJob, Threading.DispatcherPriority.Normal) |> ignore // Schedule update + element.Dispatcher.InvokeAsync(executeJob, Threading.DispatcherPriority.Background) |> ignore // Execute Update | SingleThreaded -> // If we aren't using different threads, always process normally element.Dispatcher.Invoke(fun () -> program.UpdateViewModel (vm, model)) From f8dd9ebc8e2398eecd0c5327761ff4f59e9a3d56 Mon Sep 17 00:00:00 2001 From: Joshua Marner Date: Fri, 1 Sep 2023 16:39:54 -0500 Subject: [PATCH 2/3] Bump version to 4.0.0-beta-52 --- src/Elmish.WPF/Elmish.WPF.fsproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elmish.WPF/Elmish.WPF.fsproj b/src/Elmish.WPF/Elmish.WPF.fsproj index bdae1788..99c7dfd2 100644 --- a/src/Elmish.WPF/Elmish.WPF.fsproj +++ b/src/Elmish.WPF/Elmish.WPF.fsproj @@ -18,7 +18,7 @@ https://github.com/elmish/Elmish.WPF WPF F# fsharp Elmish Elm elmish-wpf-logo-128x128.png - 4.0.0-beta-50 + 4.0.0-beta-52 https://github.com/elmish/Elmish.WPF/blob/master/RELEASE_NOTES.md $(OtherFlags) --warnon:1182 From aa83d34480b21ca913ed625f561ee1c6c4cae704 Mon Sep 17 00:00:00 2001 From: Joshua Marner Date: Fri, 1 Sep 2023 16:40:04 -0500 Subject: [PATCH 3/3] Add release notes --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c366a6b4..5e620502 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +#### 4.0.0-beta-52 +* Fixed a bug in the threaded dispatch case that could result in an out-of-date model being updated. +* Skip intermediate updates to view model when they queue up in the threaded case. + #### 4.0.0-beta-50 * Upgraded to Elmish v4 * **BREAKING:** Changed syntax of `WpfProgram.withSubscription` to now take named list of `Subscribe<'msg> = Dispatch<'msg> -> IDisposable` (note the `IDisposable` return). This function now gets called every time `'model` updates (previously it was only called on startup). This allows starting and stopping of subscriptions from this function by the given string list identifier.