mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Fix UI blocking by running plugin queries on thread pool
- Wrap each plugin query in Task.Run() to ensure synchronous plugin code doesn't block the UI thread - Show results progressively as each plugin completes using ConcurrentBag - Update UI after each plugin returns instead of waiting for all plugins
This commit is contained in:
parent
f3d3f80db8
commit
2466b907c4
1 changed files with 70 additions and 38 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -131,60 +132,91 @@ public partial class MainViewModel : ObservableObject
|
||||||
|
|
||||||
if (plugins.Count == 0) { HasResults = false; return; }
|
if (plugins.Count == 0) { HasResults = false; return; }
|
||||||
|
|
||||||
// Query all plugins in parallel and collect results
|
// Use a thread-safe collection to accumulate results from all plugins
|
||||||
var tasks = plugins.Select(p => QueryPluginAsync(p, query, token));
|
var allResults = new ConcurrentBag<ResultViewModel>();
|
||||||
var pluginResults = await Task.WhenAll(tasks);
|
|
||||||
|
|
||||||
if (token.IsCancellationRequested) return;
|
// Query all plugins in parallel - results shown progressively as each completes
|
||||||
|
var tasks = plugins.Select(async plugin =>
|
||||||
// Flatten, sort by score, take top N, and replace all at once
|
|
||||||
var allResults = pluginResults
|
|
||||||
.SelectMany(r => r)
|
|
||||||
.OrderByDescending(r => r.Score)
|
|
||||||
.Take(_settings.MaxResultsToShow)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Replace results with minimal UI updates (EditDiff)
|
|
||||||
await global::Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
{
|
||||||
Results.ReplaceResults(allResults);
|
var pluginResults = await QueryPluginAsync(plugin, query, token);
|
||||||
HasResults = Results.Results.Count > 0;
|
if (token.IsCancellationRequested) return;
|
||||||
|
|
||||||
|
// Add results to the bag
|
||||||
|
foreach (var r in pluginResults)
|
||||||
|
{
|
||||||
|
allResults.Add(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI with current accumulated results (progressive update)
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await UpdateResultsOnUIThread(allResults, token);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
|
// Final update after all plugins complete
|
||||||
|
if (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await UpdateResultsOnUIThread(allResults, token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { }
|
catch (OperationCanceledException) { }
|
||||||
catch (Exception e) { Log.Exception(ClassName, "Query error", e); }
|
catch (Exception e) { Log.Exception(ClassName, "Query error", e); }
|
||||||
finally { if (!token.IsCancellationRequested) IsQueryRunning = false; }
|
finally { if (!token.IsCancellationRequested) IsQueryRunning = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ResultViewModel>> QueryPluginAsync(PluginPair plugin, Query query, CancellationToken token)
|
private async Task UpdateResultsOnUIThread(ConcurrentBag<ResultViewModel> allResults, CancellationToken token)
|
||||||
{
|
{
|
||||||
var resultList = new List<ResultViewModel>();
|
if (token.IsCancellationRequested) return;
|
||||||
|
|
||||||
try
|
var sortedResults = allResults
|
||||||
|
.OrderByDescending(r => r.Score)
|
||||||
|
.Take(_settings.MaxResultsToShow)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
await global::Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var delay = plugin.Metadata.SearchDelayTime ?? _settings.SearchDelayTime;
|
if (token.IsCancellationRequested) return;
|
||||||
if (delay > 0) await Task.Delay(delay, token);
|
Results.ReplaceResults(sortedResults);
|
||||||
if (token.IsCancellationRequested) return resultList;
|
HasResults = Results.Results.Count > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var results = await PluginManager.QueryForPluginAsync(plugin, query, token);
|
private Task<List<ResultViewModel>> QueryPluginAsync(PluginPair plugin, Query query, CancellationToken token)
|
||||||
if (token.IsCancellationRequested || results == null || results.Count == 0) return resultList;
|
{
|
||||||
|
// Run entirely on thread pool to avoid blocking UI if plugin has synchronous code
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var resultList = new List<ResultViewModel>();
|
||||||
|
|
||||||
foreach (var r in results)
|
try
|
||||||
{
|
{
|
||||||
resultList.Add(new ResultViewModel
|
var delay = plugin.Metadata.SearchDelayTime ?? _settings.SearchDelayTime;
|
||||||
{
|
if (delay > 0) await Task.Delay(delay, token);
|
||||||
Title = r.Title ?? "",
|
if (token.IsCancellationRequested) return resultList;
|
||||||
SubTitle = r.SubTitle ?? "",
|
|
||||||
IconPath = r.IcoPath ?? plugin.Metadata.IcoPath ?? "",
|
|
||||||
Score = r.Score,
|
|
||||||
PluginResult = r
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) { }
|
|
||||||
catch (Exception e) { Log.Exception(ClassName, $"Plugin {plugin.Metadata.Name} error", e); }
|
|
||||||
|
|
||||||
return resultList;
|
var results = await PluginManager.QueryForPluginAsync(plugin, query, token);
|
||||||
|
if (token.IsCancellationRequested || results == null || results.Count == 0) return resultList;
|
||||||
|
|
||||||
|
foreach (var r in results)
|
||||||
|
{
|
||||||
|
resultList.Add(new ResultViewModel
|
||||||
|
{
|
||||||
|
Title = r.Title ?? "",
|
||||||
|
SubTitle = r.SubTitle ?? "",
|
||||||
|
IconPath = r.IcoPath ?? plugin.Metadata.IcoPath ?? "",
|
||||||
|
Score = r.Score,
|
||||||
|
PluginResult = r
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception e) { Log.Exception(ClassName, $"Plugin {plugin.Metadata.Name} error", e); }
|
||||||
|
|
||||||
|
return resultList;
|
||||||
|
}, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue