使用 AsParallel() 时无法获取与 Windows 应用关联的文件的文件图标

Can't get file icon for files associated with Windows apps when using AsParallel()

我想显示与文件关联的图标。这对于与普通桌面应用程序关联的文件类型不是问题,但仅对于与 (metro/modern) 应用程序关联的文件类型。

如果文件类型与应用相关联并且我正在使用AsParallel(),我只会得到默认的未知文件类型图标。澄清一下,我没有得到 null 或空图标,而是显示空纸的默认图标 sheet。没有 AsParallel() 我得到正确的图标。

我尝试了其他几种获取图标的方法,例如 SHGetFileInfo() 或直接通过 dll 调用 ExtractAssociatedIcon()。行为总是一样的。

示例:如果 'Adobe Acrobat' 是 PDF 文件的默认应用程序,那么在这两种情况下我都会得到正确的 Adob​​e PDF 图标。如果来自 Windows 8 或 10 的内置(现代 UI)应用 'Reader' 是默认应用,应用 AsParallel() 时我会看到未知文件类型图标。

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>

对应代码:

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;
}

用法说明:仅使用两种变体中的一种。如果两者都执行,即使使用不同的变量,问题也可能无法重现。

那是因为 SHGetFileInfo(或内部使用 SHGetFileInfoExtractAssociatedIcon)仅适用于 STA 线程(单线程单元)。在 MTA 线程(多线程单元)上,它只是 returns 默认图标。 AsParallel 使用线程池中的工作线程,它们是 MTA。

is basically correct and using STA threads solves the problem. Knowing the cause of the problem, this answer 向我暗示了正确的方向。通过使用 STA 线程的 TaskScheduler,我可以使用 Parallel.ForEach() 到 运行 我的代码。

使用 here (more infos: MSDN blog article 中的 StaTaskScheduler,下面的代码解决了我的问题。

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();