From 76dfe3814a6a5e028238563eee2c5e4d29152864 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 5 May 2025 16:14:22 +0800 Subject: [PATCH 1/4] Add test codes --- Flow.Launcher/ViewModel/MainViewModel.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 228e66edb25..4137e7f82a7 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1341,14 +1341,22 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) if (token.IsCancellationRequested) return; } + App.API.LogDebug(ClassName, $"1"); + // Since it is wrapped within a ThreadPool Thread, the synchronous context is null // Task.Yield will force it to run in ThreadPool await Task.Yield(); + App.API.LogDebug(ClassName, $"2"); + var results = await PluginManager.QueryForPluginAsync(plugin, query, token); + App.API.LogDebug(ClassName, $"3"); + if (token.IsCancellationRequested) return; + App.API.LogDebug(ClassName, $"4"); + IReadOnlyList resultsCopy; if (results == null) { @@ -1360,6 +1368,8 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) resultsCopy = DeepCloneResults(results, token); } + App.API.LogDebug(ClassName, $"5"); + foreach (var result in resultsCopy) { if (string.IsNullOrEmpty(result.BadgeIcoPath)) @@ -1368,6 +1378,8 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) } } + App.API.LogDebug(ClassName, $"6"); + if (token.IsCancellationRequested) return; App.API.LogDebug(ClassName, $"Update results for plugin <{plugin.Metadata.Name}>"); From cbaf76fd84f683bf3902123acd7bb9b40f39c388 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 5 May 2025 16:16:22 +0800 Subject: [PATCH 2/4] Add test codes --- Flow.Launcher.Core/Plugin/PluginManager.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 72303c8b754..afa0a67bfb3 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.DependencyInjection; +using Droplex; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; @@ -276,27 +277,44 @@ public static ICollection ValidPluginsForQuery(Query query) public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token) { + API.LogDebug(ClassName, $"a"); + var results = new List(); var metadata = pair.Metadata; try { + API.LogDebug(ClassName, $"b"); + var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", async () => results = await pair.Plugin.QueryAsync(query, token).ConfigureAwait(false)); + API.LogDebug(ClassName, $"c"); + token.ThrowIfCancellationRequested(); + + API.LogDebug(ClassName, $"d"); + if (results == null) return null; + + API.LogDebug(ClassName, $"e"); + UpdatePluginMetadata(results, metadata, query); + API.LogDebug(ClassName, $"f"); + metadata.QueryCount += 1; metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; token.ThrowIfCancellationRequested(); + + API.LogDebug(ClassName, $"g"); } catch (OperationCanceledException) { // null will be fine since the results will only be added into queue if the token hasn't been cancelled + API.LogDebug(ClassName, $"h"); return null; } catch (Exception e) From e6773aba4e1f566d44f141a24af249074ff334a7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 5 May 2025 16:18:18 +0800 Subject: [PATCH 3/4] Add test codes --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index afa0a67bfb3..9a5957b6944 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -319,6 +319,7 @@ public static async Task> QueryForPluginAsync(PluginPair pair, Quer } catch (Exception e) { + API.LogDebug(ClassName, $"i"); Result r = new() { Title = $"{metadata.Name}: Failed to respond!", @@ -333,6 +334,7 @@ public static async Task> QueryForPluginAsync(PluginPair pair, Quer }; results.Add(r); } + API.LogDebug(ClassName, $"j"); return results; } From 736086a4f9d6f5c052c9b0a5f1e080b242ecc54b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 6 May 2025 13:55:02 +0800 Subject: [PATCH 4/4] Dispose _updateSource when creating new one & Use _updateToken instead of _updateSource.Token --- Flow.Launcher/ViewModel/MainViewModel.cs | 29 ++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 4137e7f82a7..cca3b9e4a3a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -34,7 +34,7 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private bool _isQueryRunning; private Query _lastQuery; private string _queryTextBeforeLeaveResults; - private string _ignoredQueryText = null; + private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results private readonly FlowLauncherJsonStorage _historyItemsStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; @@ -45,6 +45,7 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private readonly TopMostRecord _topMostRecord; private CancellationTokenSource _updateSource; // Used to cancel old query flows + private CancellationToken _updateToken; // Used to avoid ObjectDisposedException of _updateSource.Token private ChannelWriter _resultsUpdateChannelWriter; private Task _resultsViewUpdateTask; @@ -60,6 +61,9 @@ public MainViewModel() _queryTextBeforeLeaveResults = ""; _queryText = ""; _lastQuery = new Query(); + _ignoredQueryText = null; + _updateSource = new CancellationTokenSource(); + _updateToken = _updateSource.Token; Settings = Ioc.Default.GetRequiredService(); Settings.PropertyChanged += (_, args) => @@ -241,7 +245,7 @@ public void RegisterResultsUpdatedEvent() return; } - var token = e.Token == default ? _updateSource.Token : e.Token; + var token = e.Token == default ? _updateToken : e.Token; // make a clone to avoid possible issue that plugin will also change the list and items when updating view model var resultsCopy = DeepCloneResults(e.Results, token); @@ -1213,7 +1217,7 @@ private void QueryHistory() private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - _updateSource?.Cancel(); + _updateSource.Cancel(); App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1239,7 +1243,9 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>"); + _updateSource.Dispose(); _updateSource = new CancellationTokenSource(); + _updateToken = _updateSource.Token; ProgressBarVisibility = Visibility.Hidden; _isQueryRunning = true; @@ -1247,7 +1253,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // Switch to ThreadPool thread await TaskScheduler.Default; - if (_updateSource.Token.IsCancellationRequested) return; + if (_updateToken.IsCancellationRequested) return; // Update the query's IsReQuery property to true if this is a re-query query.IsReQuery = isReQuery; @@ -1280,12 +1286,11 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { // Wait 15 millisecond for query change in global query // if query changes, return so that it won't be calculated - await Task.Delay(15, _updateSource.Token); - if (_updateSource.Token.IsCancellationRequested) - return; + await Task.Delay(15, _updateToken); + if (_updateToken.IsCancellationRequested) return; }*/ - _ = Task.Delay(200, _updateSource.Token).ContinueWith(_ => + _ = Task.Delay(200, _updateToken).ContinueWith(_ => { // start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet if (_isQueryRunning) @@ -1293,7 +1298,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b ProgressBarVisibility = Visibility.Visible; } }, - _updateSource.Token, + _updateToken, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); @@ -1301,7 +1306,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch { - false => QueryTaskAsync(plugin, _updateSource.Token), + false => QueryTaskAsync(plugin, _updateToken), true => Task.CompletedTask }).ToArray(); @@ -1315,13 +1320,13 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // nothing to do here } - if (_updateSource.Token.IsCancellationRequested) return; + if (_updateToken.IsCancellationRequested) return; // this should happen once after all queries are done so progress bar should continue // until the end of all querying _isQueryRunning = false; - if (!_updateSource.Token.IsCancellationRequested) + if (!_updateToken.IsCancellationRequested) { // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden;