5

I want to display the icons associated with files. This isn't a problem for file types associated with normal desktop applications but only for those associated with (metro/modern) apps.

If a file type is associated with an app and I am using AsParallel(), I get only the default unknown file type icon. To clarify, I don't get null or an empty icon, but the default icon that shows an empty paper sheet. Without AsParallel() I get the correct icon.

I tried several other ways to get the icon, e.g., SHGetFileInfo() or calling ExtractAssociatedIcon() directly via the dll. The behavior was always the same.

Example: If 'Adobe Acrobat' is the default application for PDF files, I get the correct Adobe PDF icon in both cases. If the built-in (modern UI) app 'Reader' from Windows 8 or 10 is the default app, I get the unknown file type icon when AsParallel() is applied.

MCVE

XAML:

<Window x:Class="FileIconTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <TextBox x:Name="TxtFilename" Text="x:\somefile.pdf"/>
        <Button Click="Button_Click">Go</Button>
        <Image x:Name="TheIcon" Stretch="None"/>
    </StackPanel>
</Window>

Corresponding code:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var list = new List<string>();
    list.Add(TxtFilename.Text);

    var icons = list.AsParallel().Select(GetIcon); // problem with apps
//  var icons = list.Select(GetIcon);              // works always
    TheIcon.Source = icons.First();
}

public static ImageSource GetIcon(string filename)
{
    var icon = System.Drawing.Icon.ExtractAssociatedIcon(filename);
    var iSource = Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());
    iSource.Freeze();
    return iSource;
}

Usage note: Use only one of the two variants. If both are executed, even with different variables, the problem may not be reproducible.

Stefan Hoffmann
  • 3,214
  • 16
  • 30

2 Answers2

4

That's because SHGetFileInfo (or ExtractAssociatedIcon, which uses SHGetFileInfo internally) only works on a STA thread (single thread apartment). On an MTA thread (multiple thread apartment), it just returns the default icon. AsParallel uses worker threads from the thread pool, which are MTA.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • But shouldn't this apply to all file types? Why are only files associated with apps returning the default icon? – Stefan Hoffmann Aug 11 '15 at 01:12
  • @StefanHoffmann, do you mean file types associated with an icon handler? i.e. the icon can be different based on the content of the file? I think I remember something about this limitation applying only to file types with an associated icon handler, but I can no longer reproduce the problem. – Thomas Levesque Aug 11 '15 at 09:59
  • 1
    no, not content dependent. Let's take pdf files as an example. If Acrobat Reader is the default application, I get the adobe pdf icon just fine. Even when using AsParallel(). If the built-in modern-ui app 'Reader' of Windows 8 or 10 is the default app, I get the default icon with AsParallel() but the correct icon without AsParallel(). – Stefan Hoffmann Aug 11 '15 at 12:50
  • @StefanHoffmann, I just tried with a PDF file (the Windows reader is my default app for PDF), and I can't repro the issue... I'm getting the correct icon in both cases – Thomas Levesque Aug 11 '15 at 14:13
  • hmm, strange... I can reproduce it on 3 machines. Did you alter the example code to try both variants at the same time? The problem disappears when the non-parallel method is called before the parallel one. I guess some kind of optimization or caching kicks in. – Stefan Hoffmann Aug 11 '15 at 15:02
  • 1
    @StefanHoffmann, you're right. If I change the order I can now reproduce the problem. And indeed I can see that it happens for some file types and not for others. Anyway, it always works if you get the icon from an STA thread (unless you did it from an MTA thread first). And since SHGetFileInfo is not thread safe, you should serialize the calls on an STA thread. – Thomas Levesque Aug 11 '15 at 22:42
2

Thomas' answer is basically correct and using STA threads solves the problem. Knowing the cause of the problem, this answer hinted me in the right direction. With a TaskScheduler that uses STA threads, I can use Parallel.ForEach() to run my code.

With the StaTaskScheduler from here (more infos: MSDN blog article), the following code solves my problem.

var list = new List<string>();
list.Add(TxtFilename.Text);

var ts = new StaTaskScheduler(Environment.ProcessorCount); // can be saved for later reuse

var icons = new ConcurrentBag<ImageSource>();
var pOptions = new ParallelOptions { TaskScheduler = ts };

Parallel.ForEach(list, pOptions, file => icons.Add(GetIcon(file)));
TheIcon.Source = icons.First();
Community
  • 1
  • 1
Stefan Hoffmann
  • 3,214
  • 16
  • 30