From d536377329b5e72bcb4b14e1548b10683df5f055 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Thu, 5 May 2016 21:15:13 +0100 Subject: [PATCH] Refactoring multithreading 1. ThreadPool -> Task 2. fix deadlock 3. remove unnecessory application.dispatcher.invoke 4. enable non-main thread access to results collection 5. Misc 6. part of #412 --- Plugins/Wox.Plugin.PluginManagement/Main.cs | 3 +- .../Wox.Plugin.Program/FileChangeWatcher.cs | 3 +- .../Wox.Plugin.Program/ProgramSetting.xaml.cs | 4 +- Wox.Core/Plugin/JsonRPCPlugin.cs | 22 ++--- Wox.Core/Plugin/PluginConfig.cs | 40 ++++---- Wox.Core/Plugin/PluginManager.cs | 48 +++++----- Wox.Core/Plugin/PluginsLoader.cs | 4 +- Wox.Core/Updater/UpdaterManager.cs | 3 +- Wox.CrashReporter/ReportWindow.xaml.cs | 3 +- Wox.Infrastructure/Image/ImageLoader.cs | 2 +- Wox.sln | 3 + Wox/PublicAPIInstance.cs | 7 +- Wox/SettingWindow.xaml.cs | 2 +- Wox/ViewModel/MainViewModel.cs | 93 +++++++++++-------- Wox/ViewModel/ResultsViewModel.cs | 49 ++++------ 15 files changed, 146 insertions(+), 140 deletions(-) diff --git a/Plugins/Wox.Plugin.PluginManagement/Main.cs b/Plugins/Wox.Plugin.PluginManagement/Main.cs index 93841eb82f..77599065d3 100644 --- a/Plugins/Wox.Plugin.PluginManagement/Main.cs +++ b/Plugins/Wox.Plugin.PluginManagement/Main.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Windows; using Newtonsoft.Json; @@ -144,7 +145,7 @@ namespace Wox.Plugin.PluginManagement string filePath = Path.Combine(folder, Guid.NewGuid().ToString() + ".wox"); context.API.StartLoadingBar(); - ThreadPool.QueueUserWorkItem(delegate + Task.Run(() => { using (WebClient Client = new WebClient()) { diff --git a/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs b/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs index 97194fde7d..09816fc413 100644 --- a/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs +++ b/Plugins/Wox.Plugin.Program/FileChangeWatcher.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Tasks; using Wox.Infrastructure.Logger; namespace Wox.Plugin.Program @@ -40,7 +41,7 @@ namespace Wox.Plugin.Program { if (!isIndexing) { - ThreadPool.QueueUserWorkItem(o => + Task.Run(() => { Programs.IndexPrograms(); isIndexing = false; diff --git a/Plugins/Wox.Plugin.Program/ProgramSetting.xaml.cs b/Plugins/Wox.Plugin.Program/ProgramSetting.xaml.cs index 7c41d72c62..08c8b8d4f7 100644 --- a/Plugins/Wox.Plugin.Program/ProgramSetting.xaml.cs +++ b/Plugins/Wox.Plugin.Program/ProgramSetting.xaml.cs @@ -1,5 +1,5 @@ using System.IO; -using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -31,7 +31,7 @@ namespace Wox.Plugin.Program private void ReIndexing() { programSourceView.Items.Refresh(); - ThreadPool.QueueUserWorkItem(t => + Task.Run(() => { Dispatcher.Invoke(() => { indexingPanel.Visibility = Visibility.Visible; }); Programs.IndexPrograms(); diff --git a/Wox.Core/Plugin/JsonRPCPlugin.cs b/Wox.Core/Plugin/JsonRPCPlugin.cs index 16e39c1a5a..2c6c72e1c5 100644 --- a/Wox.Core/Plugin/JsonRPCPlugin.cs +++ b/Wox.Core/Plugin/JsonRPCPlugin.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using Newtonsoft.Json; using Wox.Infrastructure.Exception; @@ -55,17 +56,14 @@ namespace Wox.Core.Plugin } else { - ThreadPool.QueueUserWorkItem(state => + string actionReponse = ExecuteCallback(result1.JsonRPCAction); + JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject(actionReponse); + if (jsonRpcRequestModel != null + && !String.IsNullOrEmpty(jsonRpcRequestModel.Method) + && jsonRpcRequestModel.Method.StartsWith("Wox.")) { - string actionReponse = ExecuteCallback(result1.JsonRPCAction); - JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject(actionReponse); - if (jsonRpcRequestModel != null - && !String.IsNullOrEmpty(jsonRpcRequestModel.Method) - && jsonRpcRequestModel.Method.StartsWith("Wox.")) - { - ExecuteWoxAPI(jsonRpcRequestModel.Method.Substring(4), jsonRpcRequestModel.Parameters); - } - }); + ExecuteWoxAPI(jsonRpcRequestModel.Method.Substring(4), jsonRpcRequestModel.Parameters); + } } } return !result1.JsonRPCAction.DontHideAfterAction; @@ -126,7 +124,7 @@ namespace Wox.Core.Plugin { using (Process process = Process.Start(startInfo)) { - if (process != null) + if (process != null) { using (StreamReader reader = process.StandardOutput) { @@ -152,7 +150,7 @@ namespace Wox.Core.Plugin } } } - catch(Exception e) + catch (Exception e) { throw new WoxJsonRPCException(e.Message); } diff --git a/Wox.Core/Plugin/PluginConfig.cs b/Wox.Core/Plugin/PluginConfig.cs index 12508831cf..8fb851c220 100644 --- a/Wox.Core/Plugin/PluginConfig.cs +++ b/Wox.Core/Plugin/PluginConfig.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using System.IO; +using System.Threading.Tasks; using Newtonsoft.Json; using Wox.Infrastructure.Exception; using Wox.Infrastructure.Logger; @@ -11,8 +13,8 @@ namespace Wox.Core.Plugin internal abstract class PluginConfig { - private const string pluginConfigName = "plugin.json"; - private static List pluginMetadatas = new List(); + private const string PluginConfigName = "plugin.json"; + private static readonly List PluginMetadatas = new List(); /// /// Parse plugin metadata in giving directories @@ -21,45 +23,41 @@ namespace Wox.Core.Plugin /// public static List Parse(string[] pluginDirectories) { - pluginMetadatas.Clear(); - foreach (string pluginDirectory in pluginDirectories) - { - ParsePluginConfigs(pluginDirectory); - } - - return pluginMetadatas; + PluginMetadatas.Clear(); + var directories = pluginDirectories.SelectMany(Directory.GetDirectories); + ParsePluginConfigs(directories); + return PluginMetadatas; } - private static void ParsePluginConfigs(string pluginDirectory) + private static void ParsePluginConfigs(IEnumerable directories) { - if (!Directory.Exists(pluginDirectory)) return; - - string[] directories = Directory.GetDirectories(pluginDirectory); - foreach (string directory in directories) + Parallel.ForEach(directories, directory => { - if (File.Exists((Path.Combine(directory, "NeedDelete.txt")))) + if (File.Exists(Path.Combine(directory, "NeedDelete.txt"))) { try { Directory.Delete(directory, true); - continue; } catch (Exception e) { Log.Fatal(e); } } - PluginMetadata metadata = GetPluginMetadata(directory); - if (metadata != null) + else { - pluginMetadatas.Add(metadata); + PluginMetadata metadata = GetPluginMetadata(directory); + if (metadata != null) + { + PluginMetadatas.Add(metadata); + } } - } + }); } private static PluginMetadata GetPluginMetadata(string pluginDirectory) { - string configPath = Path.Combine(pluginDirectory, pluginConfigName); + string configPath = Path.Combine(pluginDirectory, PluginConfigName); if (!File.Exists(configPath)) { Log.Warn($"parse plugin {configPath} failed: didn't find config file."); diff --git a/Wox.Core/Plugin/PluginManager.cs b/Wox.Core/Plugin/PluginManager.cs index 3a53b8c710..1013b1d1c8 100644 --- a/Wox.Core/Plugin/PluginManager.cs +++ b/Wox.Core/Plugin/PluginManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Wox.Core.Resource; using Wox.Core.UserSettings; using Wox.Infrastructure; @@ -31,7 +32,7 @@ namespace Wox.Core.Plugin public static IPublicAPI API { private set; get; } private static PluginsSettings _settings; private static List _metadatas; - private static readonly string[] Directories = {Infrastructure.Wox.PreinstalledDirectory, Infrastructure.Wox.UserDirectory }; + private static readonly string[] Directories = { Infrastructure.Wox.PreinstalledDirectory, Infrastructure.Wox.UserDirectory }; private static void ValidateUserDirectory() { @@ -69,43 +70,36 @@ namespace Wox.Core.Plugin ResourceMerger.UpdatePluginLanguages(); API = api; - foreach (PluginPair pluginPair in AllPlugins) + Parallel.ForEach(AllPlugins, pair => { - PluginPair pair = pluginPair; - ThreadPool.QueueUserWorkItem(o => + var milliseconds = Stopwatch.Normal($"Plugin init: {pair.Metadata.Name}", () => { - var milliseconds = Stopwatch.Normal($"Plugin init: {pair.Metadata.Name}", () => + pair.Plugin.Init(new PluginInitContext { - pair.Plugin.Init(new PluginInitContext - { - CurrentPluginMetadata = pair.Metadata, - Proxy = HttpProxy.Instance, - API = API - }); + CurrentPluginMetadata = pair.Metadata, + Proxy = HttpProxy.Instance, + API = API }); - pair.InitTime = milliseconds; - InternationalizationManager.Instance.UpdatePluginMetadataTranslations(pair); }); - } + pair.InitTime = milliseconds; + InternationalizationManager.Instance.UpdatePluginMetadataTranslations(pair); + }); - ThreadPool.QueueUserWorkItem(o => + _contextMenuPlugins = GetPluginsForInterface(); + foreach (var plugin in AllPlugins) { - _contextMenuPlugins = GetPluginsForInterface(); - foreach (var plugin in AllPlugins) + if (IsGlobalPlugin(plugin.Metadata)) { - if (IsGlobalPlugin(plugin.Metadata)) + GlobalPlugins.Add(plugin); + } + else + { + foreach (string actionKeyword in plugin.Metadata.ActionKeywords) { - GlobalPlugins.Add(plugin); - } - else - { - foreach (string actionKeyword in plugin.Metadata.ActionKeywords) - { - NonGlobalPlugins[actionKeyword] = plugin; - } + NonGlobalPlugins[actionKeyword] = plugin; } } - }); + } } diff --git a/Wox.Core/Plugin/PluginsLoader.cs b/Wox.Core/Plugin/PluginsLoader.cs index e46b468c0e..b748712fa1 100644 --- a/Wox.Core/Plugin/PluginsLoader.cs +++ b/Wox.Core/Plugin/PluginsLoader.cs @@ -15,7 +15,7 @@ namespace Wox.Core.Plugin public const string Python = "python"; public const string PythonExecutable = "pythonw.exe"; - public static IEnumerable CSharpPlugins(IEnumerable source) + public static IEnumerable CSharpPlugins(List source) { var plugins = new List(); var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.CSharp); @@ -63,7 +63,7 @@ namespace Wox.Core.Plugin return plugins; } - public static IEnumerable PythonPlugins(IEnumerable source, string pythonDirecotry) + public static IEnumerable PythonPlugins(List source, string pythonDirecotry) { var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.Python); string filename; diff --git a/Wox.Core/Updater/UpdaterManager.cs b/Wox.Core/Updater/UpdaterManager.cs index 5e83e0c404..62ea312919 100644 --- a/Wox.Core/Updater/UpdaterManager.cs +++ b/Wox.Core/Updater/UpdaterManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using NAppUpdate.Framework; using NAppUpdate.Framework.Common; @@ -80,7 +81,7 @@ namespace Wox.Core.Updater public void CheckUpdate() { - ThreadPool.QueueUserWorkItem(o => + Task.Run(() => { string json = HttpRequest.Get(VersionCheckURL, HttpProxy.Instance); if (!string.IsNullOrEmpty(json)) diff --git a/Wox.CrashReporter/ReportWindow.xaml.cs b/Wox.CrashReporter/ReportWindow.xaml.cs index 2ba0be0991..584e73f3a3 100644 --- a/Wox.CrashReporter/ReportWindow.xaml.cs +++ b/Wox.CrashReporter/ReportWindow.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Documents; using Exceptionless; @@ -40,7 +41,7 @@ namespace Wox.CrashReporter private void SendReport() { Hide(); - ThreadPool.QueueUserWorkItem(o => + Task.Run(() => { string reproduceSteps = new TextRange(tbReproduceSteps.Document.ContentStart, tbReproduceSteps.Document.ContentEnd).Text; exception.ToExceptionless() diff --git a/Wox.Infrastructure/Image/ImageLoader.cs b/Wox.Infrastructure/Image/ImageLoader.cs index f27a4b6688..478b1c7d56 100644 --- a/Wox.Infrastructure/Image/ImageLoader.cs +++ b/Wox.Infrastructure/Image/ImageLoader.cs @@ -106,7 +106,7 @@ namespace Wox.Infrastructure.Image img.Freeze(); ImageSources[icon] = img; } - Task.Factory.StartNew(() => + Task.Run(() => { Stopwatch.Debug("Preload images from cache", () => { diff --git a/Wox.sln b/Wox.sln index aa37b5d889..e4800e48e0 100644 --- a/Wox.sln +++ b/Wox.sln @@ -86,6 +86,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PreSolutionBuild", "PreSolutionBuild\PreSolutionBuild.vcxproj", "{88731DA8-C020-476E-B79A-3ADEF65ACA9F}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 diff --git a/Wox/PublicAPIInstance.cs b/Wox/PublicAPIInstance.cs index e405648e83..4bfbc41a38 100644 --- a/Wox/PublicAPIInstance.cs +++ b/Wox/PublicAPIInstance.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; +using System.Threading.Tasks; using System.Windows; -using System.Windows.Input; using NHotkey; using NHotkey.Wpf; using Wox.Core.Plugin; @@ -139,7 +139,10 @@ namespace Wox o.PluginID = plugin.ID; o.OriginQuery = query; }); - MainVM.UpdateResultView(results, plugin, query); + Task.Run(() => + { + MainVM.UpdateResultView(results, plugin, query); + }); } #endregion diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index 094a607721..b5ad5f87ac 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -47,7 +47,7 @@ namespace Wox InitializeComponent(); _settings = settings; _api = api; - ResultListBoxPreview.DataContext = new ResultsViewModel(_settings, null); + ResultListBoxPreview.DataContext = new ResultsViewModel(_settings); Loaded += Setting_Loaded; } diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs index f3ea00fc11..a4bc6d74be 100644 --- a/Wox/ViewModel/MainViewModel.cs +++ b/Wox/ViewModel/MainViewModel.cs @@ -86,11 +86,15 @@ namespace Wox.ViewModel { foreach (var pair in PluginManager.GetPluginsForInterface()) { - var plugin = (IResultUpdated) pair.Plugin; + var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); - UpdateResultView(e.Results, pair.Metadata, e.Query); + Task.Run(() => + { + + PluginManager.UpdatePluginMetadata(e.Results, pair.Metadata, e.Query); + UpdateResultView(e.Results, pair.Metadata, e.Query); + }, _updateToken); }; } } @@ -202,7 +206,10 @@ namespace Wox.ViewModel menus.Add(ContextMenuPluginInfo(id)); ContextMenu.Clear(); - ContextMenu.AddResults(menus, id); + Task.Run(() => + { + ContextMenu.AddResults(menus, id); + }, _updateToken); ContextMenuVisibility = Visibility.Visible; } else @@ -219,14 +226,14 @@ namespace Wox.ViewModel private void InitializeResultListBox() { - Results = new ResultsViewModel(_settings, _topMostRecord); + Results = new ResultsViewModel(_settings); ResultListBoxVisibility = Visibility.Collapsed; } private void InitializeContextMenu() { - ContextMenu = new ResultsViewModel(_settings, _topMostRecord); + ContextMenu = new ResultsViewModel(_settings); ContextMenuVisibility = Visibility.Collapsed; } @@ -413,7 +420,10 @@ namespace Wox.ViewModel } } ContextMenu.Clear(); - ContextMenu.AddResults(filterResults, contextMenuId); + Task.Run(() => + { + ContextMenu.AddResults(filterResults, contextMenuId); + }, _updateToken); } } @@ -455,18 +465,20 @@ namespace Wox.ViewModel }, _updateToken); var plugins = PluginManager.ValidPluginsForQuery(query); - foreach (var plugin in plugins) + Task.Run(() => { - var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; - if (!config.Disabled) + Parallel.ForEach(plugins, plugin => { - Task.Factory.StartNew(() => + var config = _settings.PluginSettings.Plugins[plugin.Metadata.ID]; + if (!config.Disabled) { + var results = PluginManager.QueryForPlugin(plugin, query); UpdateResultView(results, plugin.Metadata, query); - }, _updateToken); - } - } + } + }); + }, _updateToken); + } @@ -478,11 +490,6 @@ namespace Wox.ViewModel _queryHistory.Reset(); } - private void UpdateResultViewInternal(List list, PluginMetadata metadata) - { - Results.AddResults(list, metadata.ID); - } - private void DisplayQueryHistory(HistoryItem history) { if (history != null) @@ -495,21 +502,23 @@ namespace Wox.ViewModel var executeQueryHistoryTitle = InternationalizationManager.Instance.GetTranslation("executeQuery"); var lastExecuteTime = InternationalizationManager.Instance.GetTranslation("lastExecuteTime"); Results.RemoveResultsExcept(historyMetadata); - UpdateResultViewInternal(new List + var result = new Result { - new Result + Title = string.Format(executeQueryHistoryTitle, history.Query), + SubTitle = string.Format(lastExecuteTime, history.ExecutedDateTime), + IcoPath = "Images\\history.png", + PluginDirectory = Infrastructure.Wox.ProgramPath, + Action = _ => { - Title = string.Format(executeQueryHistoryTitle,history.Query), - SubTitle = string.Format(lastExecuteTime,history.ExecutedDateTime), - IcoPath = "Images\\history.png", - PluginDirectory = Infrastructure.Wox.ProgramPath, - Action = _ =>{ - QueryText = history.Query; - OnTextBoxSelected(); - return false; - } + QueryText = history.Query; + OnTextBoxSelected(); + return false; } - }, historyMetadata); + }; + Task.Run(() => + { + Results.AddResults(new List { result }, historyMetadata.ID); + }, _updateToken); } } private Result ContextMenuTopMost(Result result) @@ -583,21 +592,29 @@ namespace Wox.ViewModel _topMostRecordStorage.Save(); } + /// + /// To avoid deadlock, this method should not called from main thread + /// public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) { _queryHasReturn = true; ProgressBarVisibility = Visibility.Hidden; - list.ForEach(o => + foreach (var result in list) { - o.Score += _userSelectedRecord.GetSelectedCount(o) * 5; - }); + if (_topMostRecord.IsTopMost(result)) + { + result.Score = int.MaxValue; + } + else + { + result.Score += _userSelectedRecord.GetSelectedCount(result) * 5; + } + } + if (originQuery.RawQuery == _lastQuery.RawQuery) { - Application.Current.Dispatcher.Invoke(() => - { - UpdateResultViewInternal(list, metadata); - }); + Results.AddResults(list, metadata.ID); } if (list.Count > 0 && !ResultListBoxVisibility.IsVisible()) diff --git a/Wox/ViewModel/ResultsViewModel.cs b/Wox/ViewModel/ResultsViewModel.cs index cc5577b1b1..975ce52971 100644 --- a/Wox/ViewModel/ResultsViewModel.cs +++ b/Wox/ViewModel/ResultsViewModel.cs @@ -4,7 +4,9 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; using System.Windows; +using System.Windows.Data; using Wox.Core.UserSettings; using Wox.Plugin; using Wox.Storage; @@ -16,17 +18,18 @@ namespace Wox.ViewModel #region Private Fields private ResultViewModel _selectedResult; - public ResultCollection Results { get; } = new ResultCollection(); + public ResultCollection Results { get; } private Thickness _margin; - private readonly object _resultsUpdateLock = new object(); - private Settings _settings; - private TopMostRecord _topMostRecord; + private readonly object _addResultsLock = new object(); + private readonly object _collectionLock = new object(); + private readonly Settings _settings; - public ResultsViewModel(Settings settings, TopMostRecord topMostRecord) + public ResultsViewModel(Settings settings) { _settings = settings; - _topMostRecord = topMostRecord; + Results = new ResultCollection(); + BindingOperations.EnableCollectionSynchronization(Results, _collectionLock); } #endregion @@ -81,11 +84,6 @@ namespace Wox.ViewModel #region Private Methods - private bool IsTopMostResult(Result result) - { - return _topMostRecord.IsTopMost(result); - } - private int InsertIndexOf(int newScore, IList list) { int index = 0; @@ -181,45 +179,35 @@ namespace Wox.ViewModel public void RemoveResultsExcept(PluginMetadata metadata) { - lock (_resultsUpdateLock) - { - Results.RemoveAll(r => r.RawResult.PluginID != metadata.ID); - } + Results.RemoveAll(r => r.RawResult.PluginID != metadata.ID); } public void RemoveResultsFor(PluginMetadata metadata) { - lock (_resultsUpdateLock) - { - Results.RemoveAll(r => r.PluginID == metadata.ID); - } + Results.RemoveAll(r => r.PluginID == metadata.ID); } + /// + /// To avoid deadlock, this method should not called from main thread + /// public void AddResults(List newRawResults, string resultId) { - lock (_resultsUpdateLock) + lock (_addResultsLock) { var newResults = newRawResults.Select(r => new ResultViewModel(r)).ToList(); // todo use async to do new result calculation var resultsCopy = Results.ToList(); var oldResults = resultsCopy.Where(r => r.PluginID == resultId).ToList(); + // intersection of A (old results) and B (new newResults) var intersection = oldResults.Intersect(newResults).ToList(); + // remove result of relative complement of B in A foreach (var result in oldResults.Except(intersection)) { resultsCopy.Remove(result); } - // update scores - foreach (var result in newResults) - { - if (IsTopMostResult(result.RawResult)) - { - result.Score = int.MaxValue; - } - } - // update index for result in intersection of A and B foreach (var commonResult in intersection) { @@ -300,6 +288,7 @@ namespace Wox.ViewModel ResultViewModel newResult = newItems[i]; if (!oldResult.Equals(newResult)) { + this[i] = newResult; } else if (oldResult.Score != newResult.Score) @@ -308,6 +297,7 @@ namespace Wox.ViewModel } } + if (newCount > oldCount) { for (int i = oldCount; i < newCount; i++) @@ -323,7 +313,6 @@ namespace Wox.ViewModel RemoveAt(removeIndex); } } - } }