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/5] 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 abefd47bc..ac36a02ae 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; 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 @@ namespace Flow.Launcher.Core.Plugin { 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 @@ namespace Flow.Launcher.Core.Plugin // 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 000000000..5368d7677 --- /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 fb463b4d4..a5235e376 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -596,7 +596,7 @@ namespace Flow.Launcher.Core.Resource /// 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 @@ namespace Flow.Launcher.Core.Resource /// 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 da11380b8..314a751b0 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -334,7 +334,7 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher // check updates on startup await PluginInstaller.CheckForPluginUpdatesAsync((plugins) => { - Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { var pluginUpdateWindow = new PluginUpdateWindow(plugins); pluginUpdateWindow.ShowDialog(); @@ -444,7 +444,7 @@ namespace Flow.Launcher { // 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 de2579b62..45d76491d 100644 --- a/Flow.Launcher/Helper/SingleInstance.cs +++ b/Flow.Launcher/Helper/SingleInstance.cs @@ -3,6 +3,7 @@ using System.IO.Pipes; 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 @@ namespace Flow.Launcher.Helper 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 fd04b3e88..92b6d5cdd 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; 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 60517db97..9aa9724a0 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -153,7 +153,7 @@ namespace Flow.Launcher Localize.appUpdateButtonContent(), () => { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { var releaseNotesWindow = new ReleaseNotesWindow(); releaseNotesWindow.Show(); @@ -225,7 +225,7 @@ namespace Flow.Launcher { case nameof(MainViewModel.MainWindowVisibilityStatus): { - Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { if (_viewModel.MainWindowVisibilityStatus) { @@ -269,7 +269,7 @@ namespace Flow.Launcher { // 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 @@ namespace Flow.Launcher // 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 @@ namespace Flow.Launcher { DragMove(); } - }), DispatcherPriority.ApplicationIdle); + }, DispatcherPriority.ApplicationIdle); } else { @@ -726,19 +726,8 @@ namespace Flow.Launcher { 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 907bfb926..469733357 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -3,6 +3,7 @@ using System.IO; 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 @@ namespace Flow.Launcher 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 dd7d4495c..5e47d01a1 100644 --- a/Flow.Launcher/Msg.xaml.cs +++ b/Flow.Launcher/Msg.xaml.cs @@ -3,6 +3,7 @@ using System.IO; 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 @@ namespace Flow.Launcher 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 7ae53e6c5..8ee95bc57 100644 --- a/Flow.Launcher/MsgWithButton.xaml.cs +++ b/Flow.Launcher/MsgWithButton.xaml.cs @@ -3,6 +3,7 @@ using System.IO; 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 @@ namespace Flow.Launcher 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 deb5442a4..aeac09579 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher 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 119463348..7874f118e 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 @@ namespace Flow.Launcher 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 @@ namespace Flow.Launcher }; progressBox.TitleTextBlock.Text = caption; progressBox.Show(); - } + }); await reportProgressAsync(progressBox.ReportProgress).ConfigureAwait(false); } @@ -54,28 +43,20 @@ namespace Flow.Launcher } 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 55737151a..c7dec1d19 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 @@ namespace Flow.Launcher 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 12e0aa1d8..5d5f73aba 100644 --- a/Flow.Launcher/ReleaseNotesWindow.xaml.cs +++ b/Flow.Launcher/ReleaseNotesWindow.xaml.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; 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 @@ namespace Flow.Launcher private void ThemeManager_ActualApplicationThemeChanged(ThemeManager sender, object args) { - Application.Current.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { if (ThemeManager.Current.ActualApplicationTheme == ApplicationTheme.Light) { @@ -124,7 +125,7 @@ namespace Flow.Launcher { 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 d67695a75..539711452 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; 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 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel { 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 333ac3652..6dd27bca5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -16,6 +16,7 @@ using System.Windows.Threading; 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 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel if (_previousMainWindowVisibilityStatus) { // Only update the position - Application.Current?.Dispatcher.Invoke(() => + DispatcherHelper.Invoke(() => { (Application.Current?.MainWindow as MainWindow)?.UpdatePosition(); }); @@ -2112,7 +2113,7 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel } // 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 @@ namespace Flow.Launcher.ViewModel 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/5] 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 92b6d5cdd..32c5f4aea 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 469733357..14bda4c0f 100644 --- a/Flow.Launcher/MessageBoxEx.xaml.cs +++ b/Flow.Launcher/MessageBoxEx.xaml.cs @@ -30,10 +30,10 @@ namespace Flow.Launcher 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 7874f118e..cc75b0e26 100644 --- a/Flow.Launcher/ProgressBoxEx.xaml.cs +++ b/Flow.Launcher/ProgressBoxEx.xaml.cs @@ -52,10 +52,10 @@ namespace Flow.Launcher 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 6dd27bca5..757089dcb 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -764,10 +764,10 @@ namespace Flow.Launcher.ViewModel 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/5] 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 5368d7677..70fb34f9c 100644 --- a/Flow.Launcher.Core/Resource/DispatcherHelper.cs +++ b/Flow.Launcher.Core/Resource/DispatcherHelper.cs @@ -95,7 +95,7 @@ public static class DispatcherHelper } 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/5] 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 757089dcb..7a5954ad8 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -795,10 +795,10 @@ namespace Flow.Launcher.ViewModel 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/5] 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 70fb34f9c..8b2130351 100644 --- a/Flow.Launcher.Core/Resource/DispatcherHelper.cs +++ b/Flow.Launcher.Core/Resource/DispatcherHelper.cs @@ -95,7 +95,8 @@ public static class DispatcherHelper } else { - await await dispatcher.InvokeAsync(func, priority); + var task = await dispatcher.InvokeAsync(func, priority); + await task; } } }