From 03051a95cf492153e59797b6abca27d2cccff9f9 Mon Sep 17 00:00:00 2001 From: bao-qian Date: Thu, 21 Apr 2016 23:37:40 +0100 Subject: [PATCH] Refactoring image cache use parallel linq to preload images, should be faster --- Wox.Core/Wox.Core.csproj | 2 +- Wox/App.xaml.cs | 6 ++- Wox/Converters/ImagePathConverter.cs | 3 +- Wox/ImageLoader/ImageCache.cs | 32 +++++--------- Wox/ImageLoader/ImageLoader.cs | 63 +++++++++++++--------------- Wox/SettingWindow.xaml.cs | 2 +- Wox/Wox.csproj | 2 +- 7 files changed, 48 insertions(+), 62 deletions(-) diff --git a/Wox.Core/Wox.Core.csproj b/Wox.Core/Wox.Core.csproj index 02cd8f869b..3b5155f9fd 100644 --- a/Wox.Core/Wox.Core.csproj +++ b/Wox.Core/Wox.Core.csproj @@ -84,7 +84,7 @@ - + diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index d76f0d4fcc..7aded30ce1 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -23,7 +23,7 @@ namespace Wox { private const string Unique = "Wox_Unique_Application_Mutex"; public static MainWindow Window { get; private set; } - + public static ImageLoader.ImageLoader ImageLoader; public static PublicAPIInstance API { get; private set; } [STAThread] @@ -48,7 +48,9 @@ namespace Wox ThreadPool.SetMaxThreads(30, 10); ThreadPool.SetMinThreads(10, 5); - ThreadPool.QueueUserWorkItem(_ => { ImageLoader.ImageLoader.PreloadImages(); }); + + ImageLoader = new ImageLoader.ImageLoader(); + ThreadPool.QueueUserWorkItem(_ => { ImageLoader.PreloadImages(); }); PluginManager.Initialize(); diff --git a/Wox/Converters/ImagePathConverter.cs b/Wox/Converters/ImagePathConverter.cs index eb92e62514..4a5beb5b25 100644 --- a/Wox/Converters/ImagePathConverter.cs +++ b/Wox/Converters/ImagePathConverter.cs @@ -13,8 +13,7 @@ namespace Wox.Converters { return null; } - - return ImageLoader.ImageLoader.Load(value.ToString()); + return App.ImageLoader.Load(value.ToString()); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/Wox/ImageLoader/ImageCache.cs b/Wox/ImageLoader/ImageCache.cs index e317c70c9a..8c6ab12fa8 100644 --- a/Wox/ImageLoader/ImageCache.cs +++ b/Wox/ImageLoader/ImageCache.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using Wox.Infrastructure.Storage; @@ -8,39 +9,26 @@ namespace Wox.ImageLoader [Serializable] public class ImageCache { - private int counter; - private const int maxCached = 200; - public Dictionary TopUsedImages = new Dictionary(); + private const int MaxCached = 200; + public ConcurrentDictionary TopUsedImages = new ConcurrentDictionary(); public void Add(string path) { if (TopUsedImages.ContainsKey(path)) { - TopUsedImages[path] = TopUsedImages[path] + 1 ; + TopUsedImages[path] = TopUsedImages[path] + 1; } else { - TopUsedImages.Add(path, 1); + TopUsedImages[path] = 1; } - if (TopUsedImages.Count > maxCached) + if (TopUsedImages.Count > MaxCached) { - TopUsedImages = TopUsedImages.OrderByDescending(o => o.Value) - .Take(maxCached) - .ToDictionary(i => i.Key, i => i.Value); - } - - if (++counter == 30) - { - counter = 0; - } - } - - public void Remove(string path) - { - if (TopUsedImages.ContainsKey(path)) - { - TopUsedImages.Remove(path); + var images = TopUsedImages.OrderByDescending(o => o.Value) + .Take(MaxCached) + .ToDictionary(i => i.Key, i => i.Value); + TopUsedImages = new ConcurrentDictionary(images); } } } diff --git a/Wox/ImageLoader/ImageLoader.cs b/Wox/ImageLoader/ImageLoader.cs index 17e7974dea..fe655b4c82 100644 --- a/Wox/ImageLoader/ImageLoader.cs +++ b/Wox/ImageLoader/ImageLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.IO; @@ -7,6 +8,7 @@ using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; +using System.Linq; using Wox.Infrastructure; using Wox.Infrastructure.Storage; @@ -14,9 +16,9 @@ namespace Wox.ImageLoader { public class ImageLoader { - private static readonly Dictionary ImageSources = new Dictionary(); + private readonly ConcurrentDictionary ImageSources = new ConcurrentDictionary(); - private static readonly List imageExts = new List + private readonly List ImageExts = new List { ".png", ".jpg", @@ -27,7 +29,7 @@ namespace Wox.ImageLoader ".ico" }; - private static readonly List selfExts = new List + private readonly List SelfExts = new List { ".exe", ".lnk", @@ -37,10 +39,10 @@ namespace Wox.ImageLoader ".appref-ms" }; - private static readonly ImageCache _cache; - private static readonly BinaryStorage _storage; + private readonly ImageCache _cache; + private readonly BinaryStorage _storage; - static ImageLoader() + public ImageLoader() { _storage = new BinaryStorage(); _cache = _storage.Load(); @@ -51,7 +53,7 @@ namespace Wox.ImageLoader _storage.Save(); } - private static ImageSource GetIcon(string fileName) + private ImageSource GetIcon(string fileName) { try { @@ -68,32 +70,28 @@ namespace Wox.ImageLoader return null; } - public static void PreloadImages() + public void PreloadImages() { - //ImageCacheStroage.Instance.TopUsedImages can be changed during foreach, so we need to make a copy - var imageList = new Dictionary(_cache.TopUsedImages); - Stopwatch.Debug($"Preload {imageList.Count} images", () => + Stopwatch.Debug($"Preload {_cache.TopUsedImages.Count} images", () => { - foreach (var image in imageList) - { - if (!ImageSources.ContainsKey(image.Key)) - { - ImageSource img = Load(image.Key, false); - if (img != null) - { - img.Freeze(); //to make it copy to UI thread - if (!ImageSources.ContainsKey(image.Key)) - { - KeyValuePair copyedImg = image; - ImageSources.Add(copyedImg.Key, img); - } - } - } - } + _cache.TopUsedImages.AsParallel().Where(i => !ImageSources.ContainsKey(i.Key)).ForAll(i => + { + var img = Load(i.Key, false); + if (img != null) + { + // todo happlebao magic + // the image created on other threads can be accessed from main ui thread, + // this line made it possible + // should be changed the Dispatcher.InvokeAsync in the future + img.Freeze(); + + ImageSources[i.Key] = img; + } + }); }); } - public static ImageSource Load(string path, bool addToCache = true) + public ImageSource Load(string path, bool addToCache = true) { if (string.IsNullOrEmpty(path)) return null; ImageSource img = null; @@ -118,21 +116,20 @@ namespace Wox.ImageLoader { img = new BitmapImage(new Uri(path)); } - else if (selfExts.Contains(ext) && File.Exists(path)) + else if (SelfExts.Contains(ext) && File.Exists(path)) { img = GetIcon(path); } - else if (!string.IsNullOrEmpty(path) && imageExts.Contains(ext) && File.Exists(path)) + else if (!string.IsNullOrEmpty(path) && ImageExts.Contains(ext) && File.Exists(path)) { img = new BitmapImage(new Uri(path)); } - if (img != null && addToCache) { if (!ImageSources.ContainsKey(path)) { - ImageSources.Add(path, img); + ImageSources[path] = img; } } } @@ -141,7 +138,7 @@ namespace Wox.ImageLoader } // http://blogs.msdn.com/b/oldnewthing/archive/2011/01/27/10120844.aspx - private static Icon GetFileIcon(string name) + private Icon GetFileIcon(string name) { SHFILEINFO shfi = new SHFILEINFO(); uint flags = SHGFI_SYSICONINDEX; diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index 151a8d338b..adc626a9df 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -550,7 +550,7 @@ namespace Wox pluginAuthor.Text = InternationalizationManager.Instance.GetTranslation("author") + ": " + pair.Metadata.Author; pluginSubTitle.Text = pair.Metadata.Description; pluginId = pair.Metadata.ID; - pluginIcon.Source = ImageLoader.ImageLoader.Load(pair.Metadata.FullIcoPath); + pluginIcon.Source = App.ImageLoader.Load(pair.Metadata.FullIcoPath); var customizedPluginConfig = _settings.PluginSettings[pluginId]; cbDisablePlugin.IsChecked = customizedPluginConfig != null && customizedPluginConfig.Disabled; diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index 3a96cbe4fa..941f5017a9 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -125,7 +125,7 @@ - +