From 5314af937f3a6247c27713c568e7829cf04618e9 Mon Sep 17 00:00:00 2001 From: Peter Kirmeier Date: Sat, 23 Sep 2023 19:12:54 +0200 Subject: [PATCH] Improve icon loading performance Revert always loading common file icon extensions in sync. (It slows down load time of sub menu significantly, so keep using async) Fix preferring persistent icons for watcher updates. Simplified icon loading routines. --- Business/Menus.cs | 4 +- DataClasses/RowData.cs | 25 ++------- Utilities/File/IconReader.cs | 98 +++++++++++++++++++++--------------- 3 files changed, 65 insertions(+), 62 deletions(-) diff --git a/Business/Menus.cs b/Business/Menus.cs index 0017a83..7f8ac15 100644 --- a/Business/Menus.cs +++ b/Business/Menus.cs @@ -726,7 +726,7 @@ namespace SystemTrayMenu.Business IconReader.RemoveIconFromCache(rowData.Path); rowDataRenamed.HiddenEntry = hasHiddenFlag; - rowDataRenamed.LoadIcon(false); + rowDataRenamed.LoadIcon(true); rowDatas.Add(rowDataRenamed); } else @@ -806,7 +806,7 @@ namespace SystemTrayMenu.Business } rowData.HiddenEntry = hasHiddenFlag; - rowData.LoadIcon(false); + rowData.LoadIcon(true); var items = (List)menu.GetDataGridView().Items.SourceCollection; List rowDatas = new(items.Count + 1) { rowData }; diff --git a/DataClasses/RowData.cs b/DataClasses/RowData.cs index 4b6a953..fe550f4 100644 --- a/DataClasses/RowData.cs +++ b/DataClasses/RowData.cs @@ -229,20 +229,9 @@ namespace SystemTrayMenu.DataClasses /// Name of the changing property. public void CallPropertyChanged([CallerMemberName] string? propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - internal void LoadIcon(bool synchronousLoading) + internal void LoadIcon(bool isMainMenu) { - bool cacheHit; - - if (IsPointingToFolder) - { - cacheHit = IconReader.GetFolderIconWithCache(Path, ShowOverlay, Level == 0, UpdateFinalIcon, synchronousLoading); - } - else - { - cacheHit = IconReader.GetFileIconWithCache(Path, ResolvedPath, ShowOverlay, Level == 0, UpdateFinalIcon, synchronousLoading); - } - - if (!cacheHit) + if (!IconReader.GetIconAsync(IsPointingToFolder, Path, ResolvedPath, ShowOverlay, isMainMenu, UpdateFinalIcon, isMainMenu)) { IconLoading = true; ColumnIcon = IconReader.LoadingImage; // TODO: Maybe add rotation animation like for the loading Menu icon? (See: pictureBoxLoading, LoadingRotation) @@ -385,16 +374,12 @@ namespace SystemTrayMenu.DataClasses } } - private void UpdateFinalIcon(BitmapSource? icon) + private void UpdateFinalIcon(BitmapSource icon) { - if (icon == null) - { - icon = IconReader.NotFoundImage; - } - else if (HiddenEntry) + if (HiddenEntry) { icon = ImagingHelper.ApplyOpactiy(icon, 0.5d); - icon?.Freeze(); // Make it accessible for any thread + icon.Freeze(); // Make it accessible for any thread } IconLoading = false; diff --git a/Utilities/File/IconReader.cs b/Utilities/File/IconReader.cs index 56778cf..9b80bcd 100644 --- a/Utilities/File/IconReader.cs +++ b/Utilities/File/IconReader.cs @@ -29,27 +29,29 @@ namespace SystemTrayMenu.Utilities private static readonly ConcurrentDictionary IconDictPersistent = new(); private static readonly ConcurrentDictionary IconDictCache = new(); - private static readonly BlockingCollection IconFactoryQueue = new(); + private static readonly BlockingCollection IconFactoryQueueSTA = new(); private static readonly List IconFactoryThreadPoolSTA = new(16); internal static void Startup() { for (int i = 0; i < IconFactoryThreadPoolSTA.Capacity; i++) { - Thread thread = new(IconFactoryWorkerSTA); - thread.Name = "IconFactory STA #" + i.ToString(); + Thread thread = new(IconFactoryWorkerSTA) + { + Name = "IconFactory STA #" + i.ToString(), + }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); IconFactoryThreadPoolSTA.Add(thread); } - void IconFactoryWorkerSTA() + static void IconFactoryWorkerSTA() { while (true) { try { - IconFactoryQueue.Take()(); + IconFactoryQueueSTA.Take()(); } catch (ThreadInterruptedException) { @@ -89,48 +91,64 @@ namespace SystemTrayMenu.Utilities internal static void RemoveIconFromCache(string path) => IconDictPersistent.Remove(path, out _); - internal static bool GetFileIconWithCache( + /// + /// Loads an Icon requested by parameters. + /// + /// Icon is a folder or file icon. + /// Path to the file or directory entry. + /// Path to the file or directory entry which 'path' is pointing at. + /// Apply the link overlay to the icon. + /// Load from or into persistent cache. + /// Callback called when icon got loaded. + /// Force synchronous loading. e.g. during preloading on startup. + /// True = Icon was loaded synchronously, False = Icon will be loaded in the background. + internal static bool GetIconAsync( + bool isFolder, string path, string resolvedPath, bool linkOverlay, - bool checkPersistentFirst, - Action onIconLoaded, + bool persistentEntry, + Action onIconLoaded, bool synchronousLoading) { - string key; - string extension = Path.GetExtension(path); - if (IsExtensionWithSameIcon(extension)) + string key = path; + + if (!isFolder) { - // Generic file extension - key = extension + ":" + linkOverlay; - checkPersistentFirst = true; // Always store them in persistent cache - synchronousLoading = true; // Always load them after another - } - else - { - key = path; + string extension = Path.GetExtension(path); + if (IsExtensionWithSameIcon(extension)) + { + // Generic file extension + key = extension + ":" + linkOverlay; + persistentEntry = true; // Always store them in persistent cache + } } - return CacheGetOrAddIcon(path, checkPersistentFirst, onIconLoaded, synchronousLoading, FactoryIconFileSTA); - - BitmapSource FactoryIconFileSTA(string keyExtension) + if (!DictIconCache(persistentEntry).TryGetValue(key, out BitmapSource? icon) && + !DictIconCache(!persistentEntry).TryGetValue(key, out icon)) { - return GetIconAsBitmapSourceSTA(path, resolvedPath, linkOverlay, false); + if (synchronousLoading) + { + icon = DictIconCache(persistentEntry).GetOrAdd(key, FactoryIconSTA); + } + else + { + IconFactoryQueueSTA.Add(() => + { + BitmapSource icon = DictIconCache(persistentEntry).GetOrAdd(key, FactoryIconSTA); + onIconLoaded(icon); + }); + + return false; + } } - } - internal static bool GetFolderIconWithCache( - string path, - bool linkOverlay, - bool checkPersistentFirst, - Action onIconLoaded, - bool synchronousLoading) - { - return CacheGetOrAddIcon(path, checkPersistentFirst, onIconLoaded, synchronousLoading, FactoryIconFolderSTA); + onIconLoaded(icon); + return true; - BitmapSource FactoryIconFolderSTA(string keyExtension) + BitmapSource FactoryIconSTA(string keyExtension) { - return GetIconAsBitmapSourceSTA(path, path, linkOverlay, true); + return GetIconAsBitmapSourceSTA(path, resolvedPath, linkOverlay, isFolder); } } @@ -147,23 +165,23 @@ namespace SystemTrayMenu.Utilities private static bool CacheGetOrAddIcon( string key, - bool checkPersistentFirst, + bool persistentEntry, Action onIconLoaded, bool synchronousLoading, Func factory) { - if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out BitmapSource? icon) && - !DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon)) + if (!DictIconCache(persistentEntry).TryGetValue(key, out BitmapSource? icon) && + !DictIconCache(!persistentEntry).TryGetValue(key, out icon)) { if (synchronousLoading) { - icon = DictIconCache(checkPersistentFirst).GetOrAdd(key, factory); + icon = DictIconCache(persistentEntry).GetOrAdd(key, factory); } else { - IconFactoryQueue.Add(() => + IconFactoryQueueSTA.Add(() => { - BitmapSource icon = DictIconCache(checkPersistentFirst).GetOrAdd(key, factory); + BitmapSource icon = DictIconCache(persistentEntry).GetOrAdd(key, factory); onIconLoaded(icon); });