Reputation: 233
I have a static class that contains a dictionary of file extensions and BitMapSource objects. That class contains a function that given a FileInfo object will return the associated BitMapSource object, if it's not already in the dictionary, it will get it and put it in the dictionary before returning it.
This works fine when executed from the GUI thread. However, when I try to put it in a background thread I am not getting anything back. Is there any reason that I shouldn't be able to execute this from a background thread?
Static Class
namespace Test.Classes
{
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
public static class IconMap
{
private static Dictionary<string, BitmapSource> iconDictionary = new Dictionary<string, BitmapSource>();
public static BitmapSource GetFileIcon(FileInfo fileInfo)
{
if (iconDictionary.ContainsKey(fileInfo.Extension))
{
return iconDictionary[fileInfo.Extension];
}
else
{
lock (iconDictionary)
{
Icon icon = Icon.ExtractAssociatedIcon(fileInfo.FullName);
BitmapSource bitMapSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, new Int32Rect(0, 0, icon.Width, icon.Height), BitmapSizeOptions.FromEmptyOptions());
iconDictionary.Add(fileInfo.Extension, bitMapSource);
return bitMapSource;
}
}
}
}
}
Control.cs
namespace Test.Controls
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Microsoft.Office.Interop.Outlook;
public partial class AttachedFileInfo : UserControl
{
private FileInfo file;
public AttachedFileInfo(FileInfo fileInfo)
{
this.InitializeComponent();
this.file = fileInfo;
this.FileLink.NavigateUri = new Uri(fileInfo.FullName);
this.FileName.Text = fileInfo.Name;
this.LoadFileIcon(fileInfo);
}
private async void LoadFileIcon(FileInfo fileInfo)
{
Task<BitmapSource> getFileIconTask = Task<BitmapSource>.Factory.StartNew(() =>
{
// If I change this to BitmapSource icon = null; it works as expected.
BitmapSource icon = Classes.IconMap.GetFileIcon(fileInfo);
return icon;
});
await getFileIconTask;
this.FileIcon.Source = Classes.IconMap.GetFileIcon(fileInfo);
// getFileIconTask.Result;
}
private void FileLink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(this.file.FullName);
}
}
}
Upvotes: 1
Views: 147
Reputation: 6217
Your icons are not displayed, because WPF resources can only be used from another thread, if they are freezed by calling .Freeze() method (see code bellow).
Another problem is that your GetFileIcon() method is not thread safe even if you use ConcurrentDictionary, bacase it could still happen that one thread adds icon to iconDictionary and when it leaves locked block of code, another thread could enter locked block and add icon for the same file type to the dictionary, resulting in ArgumentException. So within the lock, you must again test if icon for particulatr extension is not already present in dictionary.
private static ConcurrentDictionary<string, BitmapSource> iconDictionary = new ConcurrentDictionary<string, BitmapSource>();
public static BitmapSource GetFileIcon(FileInfo fileInfo)
{
BitmapSource bitMapSource;
if (iconDictionary.TryGetValue(fileInfo.Extension, out bitMapSource))
{
return bitMapSource;
}
else
{
lock (iconDictionary)
{
if (iconDictionary.TryGetValue(fileInfo.Extension, out bitMapSource))
return bitMapSource;
Icon icon = Icon.ExtractAssociatedIcon(fileInfo.FullName);
bitMapSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, new Int32Rect(0, 0, icon.Width, icon.Height), BitmapSizeOptions.FromEmptyOptions());
bitMapSource.Freeze();//Allows BitmapSource to be used on another thread
iconDictionary[fileInfo.Extension] = bitMapSource;
return bitMapSource;
}
}
}
Upvotes: 2