From 1d14e36ef5a17fa3c84ba80e10a8208f54082813 Mon Sep 17 00:00:00 2001 From: niksedk Date: Tue, 16 Jun 2026 08:52:59 +0200 Subject: [PATCH] Open from URL: marshal download timer tick to UI thread DownloadVideoFromUrlViewModel uses a System.Timers.Timer to poll the background download task. Its Elapsed handler runs on a thread-pool thread but writes observable properties bound to the UI (StatusText, Error) and closes the window. Touching bound state off the UI thread can throw "call from invalid thread" or leave the UI inconsistent. Marshal the whole tick to the UI thread via Dispatcher.UIThread.Post. Close() is now always called on the UI thread, so it no longer needs its own Post. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../DownloadVideoFromUrlViewModel.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs b/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs index 7a9df6b06a0..c3f4bc76ee3 100644 --- a/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs +++ b/src/ui/Features/Video/OpenFromUrl/DownloadVideoFromUrlViewModel.cs @@ -219,6 +219,15 @@ private static void TryDeleteDirectory(string path) private readonly Lock _lockObj = new(); private void OnTimerElapsed(object? sender, ElapsedEventArgs e) + { + // System.Timers.Timer raises Elapsed on a thread-pool thread, but this handler + // writes observable properties bound to the UI (StatusText/Error) and closes the + // window. Touching those off the UI thread can throw "call from invalid thread" or + // leave the UI in an inconsistent state, so marshal the whole tick to the UI thread. + Dispatcher.UIThread.Post(HandleTimerTick); + } + + private void HandleTimerTick() { lock (_lockObj) { @@ -305,15 +314,13 @@ private void CommandCancel() private void Close() { - Dispatcher.UIThread.Post(() => + // Always invoked on the UI thread (HandleTimerTick / CommandCancel). + if (!string.IsNullOrEmpty(Error)) { - if (!string.IsNullOrEmpty(Error)) - { - return; // keep window open so the user can read the error - } + return; // keep window open so the user can read the error + } - Window?.Close(); - }); + Window?.Close(); } internal void OnKeyDown(KeyEventArgs e)