diff --git a/src/DodoSSH.Client.Shell/ViewModels/UpdateViewModel.cs b/src/DodoSSH.Client.Shell/ViewModels/UpdateViewModel.cs index 520ad33..413cc5c 100644 --- a/src/DodoSSH.Client.Shell/ViewModels/UpdateViewModel.cs +++ b/src/DodoSSH.Client.Shell/ViewModels/UpdateViewModel.cs @@ -346,7 +346,22 @@ internal sealed partial class UpdateViewModel : ObservableObject, IAsyncDisposab State = UpdateState.Downloading; DownloadPercent = 0; - var progress = new Progress(percent => DownloadPercent = percent); + // Monotonic, and it has to be. Progress delivers its callbacks by posting them to the captured + // synchronisation context rather than invoking them inline, so a report can arrive after the + // download has already returned — and an unguarded assignment then puts a stale smaller number + // back on the bar and leaves it there, because nothing reports again. Seen for real: a run of this + // finished at 50 with the state already Ready. + // + // Guarding here rather than reaching for an inline IProgress, because the posting is wanted: in the + // application these callbacks come off whichever thread the updater downloads on, and an observable + // property changed off the UI thread is a binding exception rather than a stale number. + var progress = new Progress(percent => + { + if (percent > DownloadPercent) + { + DownloadPercent = percent; + } + }); await updates.DownloadAsync(found, progress, cancellationToken).ConfigureAwait(true);