From 036713c6699397478c44da912f135a15505b46b4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Mar 2026 11:48:55 +0800 Subject: [PATCH 1/6] Refactor UI thread invocation with DispatcherHelper Introduce DispatcherHelper for safe UI thread access and replace direct Dispatcher.Invoke calls across the codebase. This centralizes thread invocation logic, reduces boilerplate, and improves maintainability. Some methods are refactored for clarity and UI thread safety. --- .../Plugin/JsonRPCPluginSettings.cs | 9 +- .../Resource/DispatcherHelper.cs | 101 ++++++++++++++++++ Flow.Launcher.Core/Resource/Theme.cs | 4 +- Flow.Launcher/App.xaml.cs | 6 +- Flow.Launcher/Helper/SingleInstance.cs | 5 +- .../Helper/WallpaperPathRetrieval.cs | 10 +- Flow.Launcher/MainWindow.xaml.cs | 23 ++-- Flow.Launcher/MessageBoxEx.xaml.cs | 14 ++- Flow.Launcher/Msg.xaml.cs | 5 +- Flow.Launcher/MsgWithButton.xaml.cs | 5 +- Flow.Launcher/Notification.cs | 6 +- Flow.Launcher/ProgressBoxEx.xaml.cs | 37 ++----- Flow.Launcher/PublicAPIInstance.cs | 4 +- Flow.Launcher/ReleaseNotesWindow.xaml.cs | 5 +- .../SettingsPanePluginStoreViewModel.cs | 3 +- Flow.Launcher/ViewModel/MainViewModel.cs | 31 +++--- 16 files changed, 177 insertions(+), 91 deletions(-) create mode 100644 Flow.Launcher.Core/Resource/DispatcherHelper.cs diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs index abefd47bcb4..ac36a02ae81 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Documents; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin; @@ -99,14 +100,14 @@ public void UpdateSettings(IReadOnlyDictionary settings) { case TextBox textBox: var text = value as string ?? string.Empty; - textBox.Dispatcher.Invoke(() => textBox.Text = text); + DispatcherHelper.Invoke(() => textBox.Text = text); break; case PasswordBox passwordBox: var password = value as string ?? string.Empty; - passwordBox.Dispatcher.Invoke(() => passwordBox.Password = password); + DispatcherHelper.Invoke(() => passwordBox.Password = password); break; case ComboBox comboBox: - comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value); + DispatcherHelper.Invoke(() => comboBox.SelectedItem = value); break; case CheckBox checkBox: var isChecked = value is bool boolValue @@ -114,7 +115,7 @@ public void UpdateSettings(IReadOnlyDictionary settings) // If can parse the default value to bool, use it, otherwise use false : value is string stringValue && bool.TryParse(stringValue, out var boolValueFromString) && boolValueFromString; - checkBox.Dispatcher.Invoke(() => checkBox.IsChecked = isChecked); + DispatcherHelper.Invoke(() => checkBox.IsChecked = isChecked); break; } } diff --git a/Flow.Launcher.Core/Resource/DispatcherHelper.cs b/Flow.Launcher.Core/Resource/DispatcherHelper.cs new file mode 100644 index 00000000000..5368d7677b6 --- /dev/null +++ b/Flow.Launcher.Core/Resource/DispatcherHelper.cs @@ -0,0 +1,101 @@ +using System; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; + +namespace Flow.Launcher.Core.Resource; + +#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs + +public static class DispatcherHelper +{ + public static void Invoke(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + Invoke(Application.Current?.Dispatcher, action, priority); + } + + public static T Invoke(Func func, DispatcherPriority priority = DispatcherPriority.Normal) + { + return Invoke(Application.Current?.Dispatcher, func, priority); + } + + public static void Invoke(Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + if (dispatcher == null) return; + if (dispatcher.CheckAccess()) + { + action(); + } + else + { + dispatcher.Invoke(action, priority); + } + } + + public static T Invoke(Dispatcher dispatcher, Func func, DispatcherPriority priority = DispatcherPriority.Normal) + { + if (dispatcher == null) return default; + if (dispatcher.CheckAccess()) + { + return func(); + } + else + { + return dispatcher.Invoke(func, priority); + } + } + + public static async Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + await InvokeAsync(Application.Current?.Dispatcher, action, priority); + } + + public static async Task InvokeAsync(Func func, DispatcherPriority priority = DispatcherPriority.Normal) + { + return await InvokeAsync(Application.Current?.Dispatcher, func, priority); + } + + public static async Task InvokeAsync(Func func, DispatcherPriority priority = DispatcherPriority.Normal) + { + await InvokeAsync(Application.Current?.Dispatcher, func, priority); + } + + public static async Task InvokeAsync(Dispatcher dispatcher, Action action, DispatcherPriority priority = DispatcherPriority.Normal) + { + if (dispatcher == null) return; + if (dispatcher.CheckAccess()) + { + action(); + } + else + { + await dispatcher.InvokeAsync(action, priority); + } + } + + public static async Task InvokeAsync(Dispatcher dispatcher, Func func, DispatcherPriority priority = DispatcherPriority.Normal) + { + if (dispatcher == null) return default; + if (dispatcher.CheckAccess()) + { + return func(); + } + else + { + return await dispatcher.InvokeAsync(func, priority); + } + } + + public static async Task InvokeAsync(Dispatcher dispatcher, Func func, DispatcherPriority priority = DispatcherPriority.Normal) + { + if (dispatcher == null) return; + if (dispatcher.CheckAccess()) + { + await func(); + } + else + { + await dispatcher.InvokeAsync(func, priority); + } + } +} diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index fb463b4d4c0..a5235e37607 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -596,7 +596,7 @@ private void SetResizeBoarderThickness(Thickness? effectMargin) /// public async Task RefreshFrameAsync() { - await Application.Current.Dispatcher.InvokeAsync(() => + await DispatcherHelper.InvokeAsync(() => { // Get the actual backdrop type and drop shadow effect settings var (backdropType, useDropShadowEffect) = GetActualValue(); @@ -623,7 +623,7 @@ await Application.Current.Dispatcher.InvokeAsync(() => /// public async Task SetBlurForWindowAsync() { - await Application.Current.Dispatcher.InvokeAsync(() => + await DispatcherHelper.InvokeAsync(() => { // Get the actual backdrop type and drop shadow effect settings var (backdropType, _) = GetActualValue(); diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index da11380b861..314a751b052 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -334,7 +334,7 @@ private static void AutoPluginUpdates() var timer = new PeriodicTimer(TimeSpan.FromHours(5)); await PluginInstaller.CheckForPluginUpdatesAsync((plugins) => { - Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { var pluginUpdateWindow = new PluginUpdateWindow(plugins); pluginUpdateWindow.ShowDialog(); @@ -345,7 +345,7 @@ await PluginInstaller.CheckForPluginUpdatesAsync((plugins) => // check updates on startup await PluginInstaller.CheckForPluginUpdatesAsync((plugins) => { - Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { var pluginUpdateWindow = new PluginUpdateWindow(plugins); pluginUpdateWindow.ShowDialog(); @@ -444,7 +444,7 @@ protected virtual void Dispose(bool disposing) { // Dispose needs to be called on the main Windows thread, // since some resources owned by the thread need to be disposed. - _mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose); + DispatcherHelper.Invoke(_mainWindow?.Dispatcher, _mainWindow.Dispose); _mainVM?.Dispose(); DialogJump.Dispose(); _internationalization.Dispose(); diff --git a/Flow.Launcher/Helper/SingleInstance.cs b/Flow.Launcher/Helper/SingleInstance.cs index de2579b6290..45d76491df1 100644 --- a/Flow.Launcher/Helper/SingleInstance.cs +++ b/Flow.Launcher/Helper/SingleInstance.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows; +using Flow.Launcher.Core.Resource; // http://blogs.microsoft.co.il/arik/2010/05/28/wpf-single-instance-application/ // modified to allow single instace restart @@ -100,7 +101,9 @@ private static async Task CreateRemoteServiceAsync(string channelName) await pipeServer.WaitForConnectionAsync(); // Do an asynchronous call to ActivateFirstInstance function - Application.Current?.Dispatcher.Invoke(ActivateFirstInstance); +#pragma warning disable VSTHRD103 // Call async methods when in an async method + DispatcherHelper.Invoke(ActivateFirstInstance); +#pragma warning restore VSTHRD103 // Call async methods when in an async method // Disconect client pipeServer.Disconnect(); diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index fd04b3e88fc..92b6d5cdd9f 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -3,9 +3,9 @@ using System.IO; using System.Linq; using System.Threading; -using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Microsoft.Win32; @@ -22,11 +22,11 @@ public static class WallpaperPathRetrieval public static Brush GetWallpaperBrush() { // Invoke the method on the UI thread - if (!Application.Current.Dispatcher.CheckAccess()) - { - return Application.Current.Dispatcher.Invoke(GetWallpaperBrush); - } + return DispatcherHelper.Invoke(GetWallpaperBrush1); + } + private static Brush GetWallpaperBrush1() + { try { var wallpaperPath = Win32Helper.GetWallpaperPath(); diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 60517db9736..9aa9724a08c 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -153,7 +153,7 @@ private void OnLoaded(object sender, RoutedEventArgs e) Localize.appUpdateButtonContent(), () => { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { var releaseNotesWindow = new ReleaseNotesWindow(); releaseNotesWindow.Show(); @@ -225,7 +225,7 @@ private void OnLoaded(object sender, RoutedEventArgs e) { case nameof(MainViewModel.MainWindowVisibilityStatus): { - Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { if (_viewModel.MainWindowVisibilityStatus) { @@ -269,7 +269,7 @@ private void OnLoaded(object sender, RoutedEventArgs e) { // QueryTextBox seems to be update with a DispatcherPriority as low as ContextIdle. // To ensure QueryTextBox is up to date with QueryText from the View, we need to Dispatch with such a priority - Dispatcher.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length); + DispatcherHelper.Invoke(() => QueryTextBox.CaretIndex = QueryTextBox.Text.Length); _viewModel.QueryTextCursorMovedToEnd = false; } break; @@ -542,7 +542,7 @@ private void OnMouseDown(object sender, MouseButtonEventArgs e) // Switch to Normal state WindowState = WindowState.Normal; - Application.Current?.Dispatcher.Invoke(new Action(() => + DispatcherHelper.Invoke(() => { double normalWidth = Width; double normalHeight = Height; @@ -555,7 +555,7 @@ private void OnMouseDown(object sender, MouseButtonEventArgs e) { DragMove(); } - }), DispatcherPriority.ApplicationIdle); + }, DispatcherPriority.ApplicationIdle); } else { @@ -726,19 +726,8 @@ private void RegisterSoundEffectsEvent() { Win32Helper.RegisterSleepModeListener(() => { - if (Application.Current == null) - { - return; - } - // We must run InitSoundEffects on UI thread because MediaPlayer is a DispatcherObject - if (!Application.Current.Dispatcher.CheckAccess()) - { - Application.Current.Dispatcher.Invoke(InitSoundEffects); - return; - } - - InitSoundEffects(); + DispatcherHelper.Invoke(InitSoundEffects); }); } catch (Exception e) diff --git a/Flow.Launcher/MessageBoxEx.xaml.cs b/Flow.Launcher/MessageBoxEx.xaml.cs index 907bfb926d5..46973335710 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; namespace Flow.Launcher @@ -29,11 +30,16 @@ public static MessageBoxResult Show( MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) { - if (!Application.Current.Dispatcher.CheckAccess()) - { - return Application.Current.Dispatcher.Invoke(() => Show(messageBoxText, caption, button, icon, defaultResult)); - } + return DispatcherHelper.Invoke(() => Show1(messageBoxText, caption, button, icon, defaultResult)); + } + private static MessageBoxResult Show1( + string messageBoxText, + string caption = "", + MessageBoxButton button = MessageBoxButton.OK, + MessageBoxImage icon = MessageBoxImage.None, + MessageBoxResult defaultResult = MessageBoxResult.OK) + { try { msgBox = new MessageBoxEx(button); diff --git a/Flow.Launcher/Msg.xaml.cs b/Flow.Launcher/Msg.xaml.cs index dd7d4495c5b..5e47d01a175 100644 --- a/Flow.Launcher/Msg.xaml.cs +++ b/Flow.Launcher/Msg.xaml.cs @@ -3,6 +3,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media.Animation; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.SharedModels; @@ -80,12 +81,12 @@ public async void Show(string title, string subTitle, string iconPath) Show(); - await Dispatcher.InvokeAsync(async () => + await DispatcherHelper.InvokeAsync(async () => { if (!closing) { closing = true; - await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin); + fadeOutStoryboard.Begin(); } }); } diff --git a/Flow.Launcher/MsgWithButton.xaml.cs b/Flow.Launcher/MsgWithButton.xaml.cs index 7ae53e6c599..8ee95bc579c 100644 --- a/Flow.Launcher/MsgWithButton.xaml.cs +++ b/Flow.Launcher/MsgWithButton.xaml.cs @@ -3,6 +3,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media.Animation; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.SharedModels; @@ -82,12 +83,12 @@ public async void Show(string title, string buttonText, Action buttonAction, str Show(); - await Dispatcher.InvokeAsync(async () => + await DispatcherHelper.InvokeAsync(async () => { if (!closing) { closing = true; - await Dispatcher.InvokeAsync(fadeOutStoryboard.Begin); + fadeOutStoryboard.Begin(); } }); } diff --git a/Flow.Launcher/Notification.cs b/Flow.Launcher/Notification.cs index deb5442a4b1..aeac09579d4 100644 --- a/Flow.Launcher/Notification.cs +++ b/Flow.Launcher/Notification.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Concurrent; using System.IO; -using System.Windows; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Microsoft.Toolkit.Uwp.Notifications; @@ -41,7 +41,7 @@ internal static void Uninstall() public static void Show(string title, string subTitle, string iconPath = null) { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { ShowInternal(title, subTitle, iconPath); }); @@ -91,7 +91,7 @@ private static void LegacyShow(string title, string subTitle, string iconPath) public static void ShowWithButton(string title, string buttonText, Action buttonAction, string subTitle, string iconPath = null) { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { ShowInternalWithButton(title, buttonText, buttonAction, subTitle, iconPath); }); diff --git a/Flow.Launcher/ProgressBoxEx.xaml.cs b/Flow.Launcher/ProgressBoxEx.xaml.cs index 11946334869..7874f118e3f 100644 --- a/Flow.Launcher/ProgressBoxEx.xaml.cs +++ b/Flow.Launcher/ProgressBoxEx.xaml.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using Flow.Launcher.Core.Resource; namespace Flow.Launcher { @@ -22,19 +23,7 @@ public static async Task ShowAsync(string caption, Func, Task> re ProgressBoxEx progressBox = null; try { - if (!Application.Current.Dispatcher.CheckAccess()) - { - await Application.Current.Dispatcher.InvokeAsync(() => - { - progressBox = new ProgressBoxEx(cancelProgress) - { - Title = caption - }; - progressBox.TitleTextBlock.Text = caption; - progressBox.Show(); - }); - } - else + await DispatcherHelper.InvokeAsync(() => { progressBox = new ProgressBoxEx(cancelProgress) { @@ -42,7 +31,7 @@ await Application.Current.Dispatcher.InvokeAsync(() => }; progressBox.TitleTextBlock.Text = caption; progressBox.Show(); - } + }); await reportProgressAsync(progressBox.ReportProgress).ConfigureAwait(false); } @@ -54,28 +43,20 @@ await Application.Current.Dispatcher.InvokeAsync(() => } finally { - if (!Application.Current.Dispatcher.CheckAccess()) - { - await Application.Current.Dispatcher.InvokeAsync(() => - { - progressBox?.Close(); - }); - } - else + await DispatcherHelper.InvokeAsync(() => { progressBox?.Close(); - } + }); } } private void ReportProgress(double progress) { - if (!Application.Current.Dispatcher.CheckAccess()) - { - Application.Current.Dispatcher.Invoke(() => ReportProgress(progress)); - return; - } + DispatcherHelper.Invoke(() => ReportProgress1(progress)); + } + private void ReportProgress1(double progress) + { if (progress < 0) { ProgressBar.Value = 0; diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 55737151af3..c7dec1d196b 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; @@ -141,7 +141,7 @@ public void ShowMsgWithButton(string title, string buttonText, Action buttonActi public void OpenSettingDialog() { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { SettingWindow sw = SingletonWindowOpener.Open(); }); diff --git a/Flow.Launcher/ReleaseNotesWindow.xaml.cs b/Flow.Launcher/ReleaseNotesWindow.xaml.cs index 12e0aa1d8c6..5d5f73abaec 100644 --- a/Flow.Launcher/ReleaseNotesWindow.xaml.cs +++ b/Flow.Launcher/ReleaseNotesWindow.xaml.cs @@ -9,6 +9,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure.Http; using iNKORE.UI.WPF.Modern; @@ -28,7 +29,7 @@ public ReleaseNotesWindow() private void ThemeManager_ActualApplicationThemeChanged(ThemeManager sender, object args) { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { if (ThemeManager.Current.ActualApplicationTheme == ApplicationTheme.Light) { @@ -124,7 +125,7 @@ private async void RefreshMarkdownViewer() { var output = await GetReleaseNotesMarkdownAsync().ConfigureAwait(false); - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { RefreshProgressRing.Visibility = Visibility.Collapsed; if (string.IsNullOrEmpty(output)) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs index d67695a7580..53971145243 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -5,6 +5,7 @@ using System.Windows; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; @@ -115,7 +116,7 @@ private async Task CheckPluginUpdatesAsync() { await PluginInstaller.CheckForPluginUpdatesAsync((plugins) => { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { var pluginUpdateWindow = new PluginUpdateWindow(plugins); pluginUpdateWindow.ShowDialog(); diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 333ac3652f4..6dd27bca5b7 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -16,6 +16,7 @@ using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; @@ -763,12 +764,11 @@ private void DecreaseMaxResult() public void ChangeQueryText(string queryText, bool isReQuery = false) { // Must check access so that we will not block the UI thread which causes window visibility issue - if (!Application.Current.Dispatcher.CheckAccess()) - { - Application.Current.Dispatcher.Invoke(() => ChangeQueryText(queryText, isReQuery)); - return; - } + DispatcherHelper.Invoke(() => ChangeQueryText1(queryText, isReQuery)); + } + private void ChangeQueryText1(string queryText, bool isReQuery = false) + { if (QueryText != queryText) { // Change query text first @@ -795,12 +795,11 @@ public void ChangeQueryText(string queryText, bool isReQuery = false) private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false) { // Must check access so that we will not block the UI thread which causes window visibility issue - if (!Application.Current.Dispatcher.CheckAccess()) - { - await Application.Current.Dispatcher.InvokeAsync(() => ChangeQueryTextAsync(queryText, isReQuery)); - return; - } + await DispatcherHelper.InvokeAsync(() => ChangeQueryText1Async(queryText, isReQuery)); + } + private async Task ChangeQueryText1Async(string queryText, bool isReQuery = false) + { if (QueryText != queryText) { // Change query text first @@ -1951,10 +1950,12 @@ public async Task SetupDialogJumpAsync(nint handle) if (dialogWindowHandleChanged) { // Only update the position - Application.Current?.Dispatcher.Invoke(() => +#pragma warning disable VSTHRD103 // Call async methods when in an async method + DispatcherHelper.Invoke(() => { (Application.Current?.MainWindow as MainWindow)?.UpdatePosition(); }); +#pragma warning restore VSTHRD103 // Call async methods when in an async method _ = ResetWindowAsync(); } @@ -2049,7 +2050,7 @@ public async void ResetDialogJump() if (_previousMainWindowVisibilityStatus) { // Only update the position - Application.Current?.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { (Application.Current?.MainWindow as MainWindow)?.UpdatePosition(); }); @@ -2112,7 +2113,7 @@ public void Show() if (App.LoadingOrExiting) return; // When application is exiting, the Application.Current will be null - Application.Current?.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { // When application is exiting, the Application.Current will be null if (Application.Current?.MainWindow is MainWindow mainWindow) @@ -2194,7 +2195,7 @@ public async void Hide(bool reset = true) } // When application is exiting, the Application.Current will be null - Application.Current?.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { // When application is exiting, the Application.Current will be null if (Application.Current?.MainWindow is MainWindow mainWindow) @@ -2328,7 +2329,7 @@ public void UpdateResultView(ICollection resultsForUpdates) public void FocusQueryTextBox() { // When application is exiting, the Application.Current will be null - Application.Current?.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { // When application is exiting, the Application.Current will be null if (Application.Current?.MainWindow is MainWindow window) From 7c55986b2487674d7856a23b957646b7f91d2bba Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Mar 2026 11:57:55 +0800 Subject: [PATCH 2/6] Rename private "*1" methods to "*Core" for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored private methods previously suffixed with "1" to use the "Core" suffix instead (e.g., GetWallpaperBrush1 → GetWallpaperBrushCore). Updated all corresponding invocations. This improves naming consistency and aligns with common conventions for core logic methods. --- Flow.Launcher/Helper/WallpaperPathRetrieval.cs | 4 ++-- Flow.Launcher/MessageBoxEx.xaml.cs | 4 ++-- Flow.Launcher/ProgressBoxEx.xaml.cs | 4 ++-- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index 92b6d5cdd9f..32c5f4aea41 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -22,10 +22,10 @@ public static class WallpaperPathRetrieval public static Brush GetWallpaperBrush() { // Invoke the method on the UI thread - return DispatcherHelper.Invoke(GetWallpaperBrush1); + return DispatcherHelper.Invoke(GetWallpaperBrushCore); } - private static Brush GetWallpaperBrush1() + private static Brush GetWallpaperBrushCore() { try { diff --git a/Flow.Launcher/MessageBoxEx.xaml.cs b/Flow.Launcher/MessageBoxEx.xaml.cs index 46973335710..14bda4c0ff8 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -30,10 +30,10 @@ public static MessageBoxResult Show( MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.OK) { - return DispatcherHelper.Invoke(() => Show1(messageBoxText, caption, button, icon, defaultResult)); + return DispatcherHelper.Invoke(() => ShowCore(messageBoxText, caption, button, icon, defaultResult)); } - private static MessageBoxResult Show1( + private static MessageBoxResult ShowCore( string messageBoxText, string caption = "", MessageBoxButton button = MessageBoxButton.OK, diff --git a/Flow.Launcher/ProgressBoxEx.xaml.cs b/Flow.Launcher/ProgressBoxEx.xaml.cs index 7874f118e3f..cc75b0e2636 100644 --- a/Flow.Launcher/ProgressBoxEx.xaml.cs +++ b/Flow.Launcher/ProgressBoxEx.xaml.cs @@ -52,10 +52,10 @@ await DispatcherHelper.InvokeAsync(() => private void ReportProgress(double progress) { - DispatcherHelper.Invoke(() => ReportProgress1(progress)); + DispatcherHelper.Invoke(() => ReportProgressCore(progress)); } - private void ReportProgress1(double progress) + private void ReportProgressCore(double progress) { if (progress < 0) { diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6dd27bca5b7..757089dcb45 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -764,10 +764,10 @@ private void DecreaseMaxResult() public void ChangeQueryText(string queryText, bool isReQuery = false) { // Must check access so that we will not block the UI thread which causes window visibility issue - DispatcherHelper.Invoke(() => ChangeQueryText1(queryText, isReQuery)); + DispatcherHelper.Invoke(() => ChangeQueryTextCore(queryText, isReQuery)); } - private void ChangeQueryText1(string queryText, bool isReQuery = false) + private void ChangeQueryTextCore(string queryText, bool isReQuery = false) { if (QueryText != queryText) { From fc147c3377b6ee279ff1ec557d5234325894ecd4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Mar 2026 12:00:28 +0800 Subject: [PATCH 3/6] Await async delegates fully in DispatcherHelper Ensure that async functions invoked via dispatcher are awaited until completion, not just until scheduled, by using double await on InvokeAsync. This prevents premature continuation when the delegate itself is asynchronous. --- Flow.Launcher.Core/Resource/DispatcherHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Resource/DispatcherHelper.cs b/Flow.Launcher.Core/Resource/DispatcherHelper.cs index 5368d7677b6..70fb34f9cb4 100644 --- a/Flow.Launcher.Core/Resource/DispatcherHelper.cs +++ b/Flow.Launcher.Core/Resource/DispatcherHelper.cs @@ -95,7 +95,7 @@ public static async Task InvokeAsync(Dispatcher dispatcher, Func func, Dis } else { - await dispatcher.InvokeAsync(func, priority); + await await dispatcher.InvokeAsync(func, priority); } } } From a12645df301ca1261cf86158adf52168e366a1fc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Mar 2026 12:07:23 +0800 Subject: [PATCH 4/6] Rename ChangeQueryText1Async to ChangeQueryTextCoreAsync Refactored method name for clarity and consistency. Updated all internal references to use the new method name. No functional changes were made. --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 757089dcb45..7a5954ad8ae 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -795,10 +795,10 @@ private void ChangeQueryTextCore(string queryText, bool isReQuery = false) private async Task ChangeQueryTextAsync(string queryText, bool isReQuery = false) { // Must check access so that we will not block the UI thread which causes window visibility issue - await DispatcherHelper.InvokeAsync(() => ChangeQueryText1Async(queryText, isReQuery)); + await DispatcherHelper.InvokeAsync(() => ChangeQueryTextCoreAsync(queryText, isReQuery)); } - private async Task ChangeQueryText1Async(string queryText, bool isReQuery = false) + private async Task ChangeQueryTextCoreAsync(string queryText, bool isReQuery = false) { if (QueryText != queryText) { From 95fc3eb489c7370377311a82b67cbb6a2302aa7e Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Tue, 10 Mar 2026 12:20:45 +0800 Subject: [PATCH 5/6] Improve code quality Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Core/Resource/DispatcherHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Resource/DispatcherHelper.cs b/Flow.Launcher.Core/Resource/DispatcherHelper.cs index 70fb34f9cb4..8b213035185 100644 --- a/Flow.Launcher.Core/Resource/DispatcherHelper.cs +++ b/Flow.Launcher.Core/Resource/DispatcherHelper.cs @@ -95,7 +95,8 @@ public static async Task InvokeAsync(Dispatcher dispatcher, Func func, Dis } else { - await await dispatcher.InvokeAsync(func, priority); + var task = await dispatcher.InvokeAsync(func, priority); + await task; } } } From 66e9e6f0b490762722725d04eadb72a00960cc39 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 4 Apr 2026 18:13:40 +0800 Subject: [PATCH 6/6] Refactor plugin settings window dispatcher usage Replaced Application.Current.Dispatcher.Invoke with DispatcherHelper.Invoke when opening the plugin settings window to centralize and abstract dispatcher invocation logic. --- Flow.Launcher/PublicAPIInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 02d87373821..969bb485d7d 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -150,7 +150,7 @@ public void OpenSettingDialog() public bool OpenPluginSettingsWindow(string pluginId) { - return Application.Current.Dispatcher.Invoke(() => + return DispatcherHelper.Invoke(() => { try {