diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 6c4236db9d1..c0b74dc6870 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -35,7 +35,7 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private Query _lastQuery; private bool _lastIsHomeQuery; 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; @@ -46,6 +46,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; @@ -67,6 +68,7 @@ public MainViewModel() _queryTextBeforeLeaveResults = ""; _queryText = ""; _lastQuery = new Query(); + _ignoredQueryText = null; // null as invalid value Settings = Ioc.Default.GetRequiredService(); Settings.PropertyChanged += (_, args) => @@ -248,7 +250,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); @@ -1264,7 +1266,12 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b var isHomeQuery = query.RawQuery == string.Empty; - _updateSource = new CancellationTokenSource(); + _updateSource?.Dispose(); + + var currentUpdateSource = new CancellationTokenSource(); + _updateSource = currentUpdateSource; + var currentCancellationToken = _updateSource.Token; + _updateToken = currentCancellationToken; ProgressBarVisibility = Visibility.Hidden; _isQueryRunning = true; @@ -1272,7 +1279,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // Switch to ThreadPool thread await TaskScheduler.Default; - if (_updateSource.Token.IsCancellationRequested) return; + if (currentCancellationToken.IsCancellationRequested) return; // Update the query's IsReQuery property to true if this is a re-query query.IsReQuery = isReQuery; @@ -1321,12 +1328,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, currentCancellationToken); + if (currentCancellationToken.IsCancellationRequested) return; }*/ - _ = Task.Delay(200, _updateSource.Token).ContinueWith(_ => + _ = Task.Delay(200, currentCancellationToken).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) @@ -1334,7 +1340,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b ProgressBarVisibility = Visibility.Visible; } }, - _updateSource.Token, + currentCancellationToken, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); @@ -1345,21 +1351,21 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch { - false => QueryTaskAsync(plugin, _updateSource.Token), + false => QueryTaskAsync(plugin, currentCancellationToken), true => Task.CompletedTask }).ToArray(); // Query history results for home page firstly so it will be put on top of the results if (Settings.ShowHistoryResultsForHomePage) { - QueryHistoryTask(); + QueryHistoryTask(currentCancellationToken); } } else { tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch { - false => QueryTaskAsync(plugin, _updateSource.Token), + false => QueryTaskAsync(plugin, currentCancellationToken), true => Task.CompletedTask }).ToArray(); } @@ -1374,13 +1380,13 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // nothing to do here } - if (_updateSource.Token.IsCancellationRequested) return; + if (currentCancellationToken.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 (!currentCancellationToken.IsCancellationRequested) { // update to hidden if this is still the current query ProgressBarVisibility = Visibility.Hidden; @@ -1440,19 +1446,19 @@ await PluginManager.QueryHomeForPluginAsync(plugin, query, token) : } } - void QueryHistoryTask() + void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top var historyItems = _history.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); var results = GetHistoryItems(historyItems); - if (_updateSource.Token.IsCancellationRequested) return; + if (token.IsCancellationRequested) return; App.API.LogDebug(ClassName, $"Update results for history"); if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, _historyMetadata, query, - _updateSource.Token))) + token))) { App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); }