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.
This commit is contained in:
Peter Kirmeier 2023-09-23 19:12:54 +02:00
parent 425bd91160
commit 5314af937f
3 changed files with 65 additions and 62 deletions

View file

@ -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<RowData>)menu.GetDataGridView().Items.SourceCollection;
List<RowData> rowDatas = new(items.Count + 1) { rowData };

View file

@ -229,20 +229,9 @@ namespace SystemTrayMenu.DataClasses
/// <param name="propertyName">Name of the changing property.</param>
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;

View file

@ -29,27 +29,29 @@ namespace SystemTrayMenu.Utilities
private static readonly ConcurrentDictionary<string, BitmapSource> IconDictPersistent = new();
private static readonly ConcurrentDictionary<string, BitmapSource> IconDictCache = new();
private static readonly BlockingCollection<Action> IconFactoryQueue = new();
private static readonly BlockingCollection<Action> IconFactoryQueueSTA = new();
private static readonly List<Thread> 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(
/// <summary>
/// Loads an Icon requested by parameters.
/// </summary>
/// <param name="isFolder">Icon is a folder or file icon.</param>
/// <param name="path">Path to the file or directory entry.</param>
/// <param name="resolvedPath">Path to the file or directory entry which 'path' is pointing at.</param>
/// <param name="linkOverlay">Apply the link overlay to the icon.</param>
/// <param name="persistentEntry">Load from or into persistent cache.</param>
/// <param name="onIconLoaded">Callback called when icon got loaded.</param>
/// <param name="synchronousLoading">Force synchronous loading. e.g. during preloading on startup.</param>
/// <returns>True = Icon was loaded synchronously, False = Icon will be loaded in the background.</returns>
internal static bool GetIconAsync(
bool isFolder,
string path,
string resolvedPath,
bool linkOverlay,
bool checkPersistentFirst,
Action<BitmapSource?> onIconLoaded,
bool persistentEntry,
Action<BitmapSource> 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<BitmapSource?> 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<BitmapSource?> onIconLoaded,
bool synchronousLoading,
Func<string, BitmapSource> 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);
});