Refactoring image cache

use parallel linq to preload images, should be faster
This commit is contained in:
bao-qian 2016-04-21 23:37:40 +01:00
parent 80f31f75ad
commit 03051a95cf
7 changed files with 48 additions and 62 deletions

View File

@ -84,7 +84,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resource\FontHelper.cs" /> <Compile Include="Resource\FontHelper.cs" />
<Compile Include="Resource\Theme.cs" /> <Compile Include="Resource\Theme.cs" />
<Compile Include="UserSettings\PluginConfig.cs" /> <Compile Include="UserSettings\PluginSettings.cs" />
<Compile Include="UserSettings\PluginHotkey.cs" /> <Compile Include="UserSettings\PluginHotkey.cs" />
<Compile Include="UserSettings\Settings.cs" /> <Compile Include="UserSettings\Settings.cs" />
<Compile Include="Updater\SemanticVersion.cs" /> <Compile Include="Updater\SemanticVersion.cs" />

View File

@ -23,7 +23,7 @@ namespace Wox
{ {
private const string Unique = "Wox_Unique_Application_Mutex"; private const string Unique = "Wox_Unique_Application_Mutex";
public static MainWindow Window { get; private set; } public static MainWindow Window { get; private set; }
public static ImageLoader.ImageLoader ImageLoader;
public static PublicAPIInstance API { get; private set; } public static PublicAPIInstance API { get; private set; }
[STAThread] [STAThread]
@ -48,7 +48,9 @@ namespace Wox
ThreadPool.SetMaxThreads(30, 10); ThreadPool.SetMaxThreads(30, 10);
ThreadPool.SetMinThreads(10, 5); ThreadPool.SetMinThreads(10, 5);
ThreadPool.QueueUserWorkItem(_ => { ImageLoader.ImageLoader.PreloadImages(); });
ImageLoader = new ImageLoader.ImageLoader();
ThreadPool.QueueUserWorkItem(_ => { ImageLoader.PreloadImages(); });
PluginManager.Initialize(); PluginManager.Initialize();

View File

@ -13,8 +13,7 @@ namespace Wox.Converters
{ {
return null; return null;
} }
return App.ImageLoader.Load(value.ToString());
return ImageLoader.ImageLoader.Load(value.ToString());
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

View File

@ -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 Wox.Infrastructure.Storage; using Wox.Infrastructure.Storage;
@ -8,39 +9,26 @@ namespace Wox.ImageLoader
[Serializable] [Serializable]
public class ImageCache public class ImageCache
{ {
private int counter; private const int MaxCached = 200;
private const int maxCached = 200; public ConcurrentDictionary<string, int> TopUsedImages = new ConcurrentDictionary<string, int>();
public Dictionary<string, int> TopUsedImages = new Dictionary<string, int>();
public void Add(string path) public void Add(string path)
{ {
if (TopUsedImages.ContainsKey(path)) if (TopUsedImages.ContainsKey(path))
{ {
TopUsedImages[path] = TopUsedImages[path] + 1 ; TopUsedImages[path] = TopUsedImages[path] + 1;
} }
else else
{ {
TopUsedImages.Add(path, 1); TopUsedImages[path] = 1;
} }
if (TopUsedImages.Count > maxCached) if (TopUsedImages.Count > MaxCached)
{ {
TopUsedImages = TopUsedImages.OrderByDescending(o => o.Value) var images = TopUsedImages.OrderByDescending(o => o.Value)
.Take(maxCached) .Take(MaxCached)
.ToDictionary(i => i.Key, i => i.Value); .ToDictionary(i => i.Key, i => i.Value);
} TopUsedImages = new ConcurrentDictionary<string, int>(images);
if (++counter == 30)
{
counter = 0;
}
}
public void Remove(string path)
{
if (TopUsedImages.ContainsKey(path))
{
TopUsedImages.Remove(path);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
@ -7,6 +8,7 @@ using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Linq;
using Wox.Infrastructure; using Wox.Infrastructure;
using Wox.Infrastructure.Storage; using Wox.Infrastructure.Storage;
@ -14,9 +16,9 @@ namespace Wox.ImageLoader
{ {
public class ImageLoader public class ImageLoader
{ {
private static readonly Dictionary<string, ImageSource> ImageSources = new Dictionary<string, ImageSource>(); private readonly ConcurrentDictionary<string, ImageSource> ImageSources = new ConcurrentDictionary<string, ImageSource>();
private static readonly List<string> imageExts = new List<string> private readonly List<string> ImageExts = new List<string>
{ {
".png", ".png",
".jpg", ".jpg",
@ -27,7 +29,7 @@ namespace Wox.ImageLoader
".ico" ".ico"
}; };
private static readonly List<string> selfExts = new List<string> private readonly List<string> SelfExts = new List<string>
{ {
".exe", ".exe",
".lnk", ".lnk",
@ -37,10 +39,10 @@ namespace Wox.ImageLoader
".appref-ms" ".appref-ms"
}; };
private static readonly ImageCache _cache; private readonly ImageCache _cache;
private static readonly BinaryStorage<ImageCache> _storage; private readonly BinaryStorage<ImageCache> _storage;
static ImageLoader() public ImageLoader()
{ {
_storage = new BinaryStorage<ImageCache>(); _storage = new BinaryStorage<ImageCache>();
_cache = _storage.Load(); _cache = _storage.Load();
@ -51,7 +53,7 @@ namespace Wox.ImageLoader
_storage.Save(); _storage.Save();
} }
private static ImageSource GetIcon(string fileName) private ImageSource GetIcon(string fileName)
{ {
try try
{ {
@ -68,32 +70,28 @@ namespace Wox.ImageLoader
return null; return null;
} }
public static void PreloadImages() public void PreloadImages()
{ {
//ImageCacheStroage.Instance.TopUsedImages can be changed during foreach, so we need to make a copy Stopwatch.Debug($"Preload {_cache.TopUsedImages.Count} images", () =>
var imageList = new Dictionary<string, int>(_cache.TopUsedImages);
Stopwatch.Debug($"Preload {imageList.Count} images", () =>
{ {
foreach (var image in imageList) _cache.TopUsedImages.AsParallel().Where(i => !ImageSources.ContainsKey(i.Key)).ForAll(i =>
{ {
if (!ImageSources.ContainsKey(image.Key)) var img = Load(i.Key, false);
{ if (img != null)
ImageSource img = Load(image.Key, false); {
if (img != null) // todo happlebao magic
{ // the image created on other threads can be accessed from main ui thread,
img.Freeze(); //to make it copy to UI thread // this line made it possible
if (!ImageSources.ContainsKey(image.Key)) // should be changed the Dispatcher.InvokeAsync in the future
{ img.Freeze();
KeyValuePair<string, int> copyedImg = image;
ImageSources.Add(copyedImg.Key, img); 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; if (string.IsNullOrEmpty(path)) return null;
ImageSource img = null; ImageSource img = null;
@ -118,21 +116,20 @@ namespace Wox.ImageLoader
{ {
img = new BitmapImage(new Uri(path)); 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); 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)); img = new BitmapImage(new Uri(path));
} }
if (img != null && addToCache) if (img != null && addToCache)
{ {
if (!ImageSources.ContainsKey(path)) 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 // 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(); SHFILEINFO shfi = new SHFILEINFO();
uint flags = SHGFI_SYSICONINDEX; uint flags = SHGFI_SYSICONINDEX;

View File

@ -550,7 +550,7 @@ namespace Wox
pluginAuthor.Text = InternationalizationManager.Instance.GetTranslation("author") + ": " + pair.Metadata.Author; pluginAuthor.Text = InternationalizationManager.Instance.GetTranslation("author") + ": " + pair.Metadata.Author;
pluginSubTitle.Text = pair.Metadata.Description; pluginSubTitle.Text = pair.Metadata.Description;
pluginId = pair.Metadata.ID; 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]; var customizedPluginConfig = _settings.PluginSettings[pluginId];
cbDisablePlugin.IsChecked = customizedPluginConfig != null && customizedPluginConfig.Disabled; cbDisablePlugin.IsChecked = customizedPluginConfig != null && customizedPluginConfig.Disabled;

View File

@ -125,7 +125,7 @@
<Compile Include="Converters\StringNullOrEmptyToVisibilityConverter.cs" /> <Compile Include="Converters\StringNullOrEmptyToVisibilityConverter.cs" />
<Compile Include="Helper\VisibilityExtensions.cs" /> <Compile Include="Helper\VisibilityExtensions.cs" />
<Compile Include="Helper\SingletonWindowOpener.cs" /> <Compile Include="Helper\SingletonWindowOpener.cs" />
<Compile Include="ImageLoader\ImageCacheStroage.cs" /> <Compile Include="ImageLoader\ImageCache.cs" />
<Compile Include="NotifyIconManager.cs" /> <Compile Include="NotifyIconManager.cs" />
<Compile Include="PublicAPIInstance.cs" /> <Compile Include="PublicAPIInstance.cs" />
<Compile Include="ResultListBox.xaml.cs"> <Compile Include="ResultListBox.xaml.cs">