Question

I have image control which loads icon in different thread

It generally works but for some file extensions (xml, html...) it always returns default icon if retrieved in background thread, but correct icon when retrieved on UI thread.

Why is this happening?

        void cIconImage_Loaded(object sender, RoutedEventArgs e)
        {       
            var source = GetImage(mypath); 
            this.Source = source;       
        }

        private ImageSource GetImage(string mypath)
        {
            ImageSource imgSource = null;   
            icon = ShellIcon.GetSmallIcon(mypath);
            imgSource = icon.ToImageSource();
            imgSource.Freeze();
            return imgSource;   
        }

        private static Icon GetIcon(string fileName, SHGFI flags, bool isFolder = false)
        {
            SHFILEINFO shinfo = new SHFILEINFO();

    //////For some extensions (xml, html,...) returns default icon
    var task = Task.Factory.StartNew(() => 
Win32.SHGetFileInfo(fileName, isFolder ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint)Marshal.SizeOf(shinfo), (uint)(SHGFI.Icon | flags))
                );
    task.Wait(); //temporary 

    //////Everything OK but not in background thread
IntPtr hImgSmall = Win32.SHGetFileInfo(fileName, isFolder ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint)Marshal.SizeOf(shinfo), (uint)(SHGFI.Icon | flags));       
    //////

            Icon icon = (Icon)System.Drawing.Icon.FromHandle(shinfo.hIcon).Clone();
            Win32.DestroyIcon(shinfo.hIcon);
            return icon;
        }

GetIcon is part of this class https://gist.github.com/madd0/1433330 but I have tried many different variations of this class with the same result.

Was it helpful?

Solution

You need to specify that the thread be a single threaded apartment. You do that by calling SetApartmentState(System.Threading.ApartmentState.STA) before the thread starts. Note that you should not call CoInitialize or CoInitializeEx since that will be done automatically for you. More details on that can be found at this excellent question: Do i need to call CoInitialize before interacting with COM in .NET?

The other point to make is that SHGetFileInfo is not threadsafe. If you have multiple threads that call SHGetFileInfo then you will need to serialize those calls to SHGetFileInfo.

OTHER TIPS

This seems to work and shows all icons

void cIconImage_Loaded(object sender, RoutedEventArgs e)
{
    var thread = new System.Threading.Thread(() =>
    {
        CoInitialize((IntPtr)0);
        var source = GetImage(mypath);
        this.Dispatcher.BeginInvoke(new Action(() =>
        {
                this.Source = source;
        }), System.Windows.Threading.DispatcherPriority.Background);
        CoUninitialize();
    });
    thread.IsBackground = true;
    thread.SetApartmentState(System.Threading.ApartmentState.STA);
    thread.Start();
}

Everything else is the same (except GetIcon does not use Task.Factory)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top