在线程调用的SHGetFileInfo避免UI冻结(Calling SHGetFileInfo in

2019-06-23 12:49发布

在.NET 4.0的应用程序(WPF),我们正在使用SHGetFileInfo ,以获得一个目录树壳图标。 因为这需要在某些情况下,相当长的一段时间(即一个网络驱动器无法访问或软盘驱动器),我们想要做这一个线程,然后当它在被读取更新的图标。

呼叫基本上是相同的,它现在只是一个线程中执行。 因为有人说,线程必须STA这个工作,我们使用了线程,而不是线程池进行测试,以相同的结果。 使用ThreadPool也没有工作。

SHGetFileInfo成功(返回1),但在该结构中HICON构件是零。

IntPtr GetIcon(string name)
{
    Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
    uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES | Shell32.SHGFI_SMALLICON;

    Shell32.SHGetFileInfo(
        name, System.IO.Directory.Exists(name) ? Shell32.FILE_ATTRIBUTE_DIRECTORY : Shell32.FILE_ATTRIBUTE_NORMAL,
        ref shfi, 
        (uint) System.Runtime.InteropServices.Marshal.SizeOf(shfi), 
        flags );
    return shfi.hIcon;
}

非常相同的代码从GUI线程工作正常。 什么必须做,从一个单独的线程使函数的工作,或者,然而,使其工作,而不会阻塞GUI线程?


更新:解决这个问题的代码基本上是这样的:

var thread = new System.Threading.Thread(() => {
    var result = GetIcon("C:\\");
    // ... do something with the result
});
thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.Start();

如果只有线程委托中的线留在,它工作正常(但GUI线程,当然)。


更新:现在,我们只需调用调用SHGetFileInfo ,使其工作。 这有(直到所有的图标都被加载并没有显示与文件视图页面)最初的问题已经解决了优势,但它意味着页面挂起每个图标。 但至少现在用户看到的东西是怎么回事。 我们仍然在寻找一个实际的解决问题的办法。

Answer 1:

我不认为有任何问题。 你并不需要使用SetApartmentState。 根据该文件 ,你确实需要呼吁的CoInitialize或OleInitialize,但我认为这应该是由WPF反正叫你。

我创建了下面一个简单的WPF应用程序。 这工作得很好。 运行的SHGetFileInfo在不同的线程到UI线程和shfi.hIcon不为零。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
        Task<IntPtr> task = Task.Factory.StartNew(() => GetIcon("C:\\"));
    }

    private IntPtr GetIcon(string name)
    {
        Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);

        var shfi = new Shell32.SHFILEINFO();
        uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES | Shell32.SHGFI_SMALLICON;

        Shell32.SHGetFileInfo(
            name,
            Directory.Exists(name) ? Shell32.FILE_ATTRIBUTE_DIRECTORY : Shell32.FILE_ATTRIBUTE_NORMAL,
            ref shfi,
            (uint) Marshal.SizeOf(shfi),
            flags);

        Debug.WriteLine(shfi.hIcon);

        return shfi.hIcon;
    }
}


public class Shell32
{
    public const int MAX_PATH = 256;

    // Browsing for directory.
    public const uint BIF_RETURNONLYFSDIRS = 0x0001;
    public const uint BIF_DONTGOBELOWDOMAIN = 0x0002;
    public const uint BIF_STATUSTEXT = 0x0004;
    public const uint BIF_RETURNFSANCESTORS = 0x0008;
    public const uint BIF_EDITBOX = 0x0010;
    public const uint BIF_VALIDATE = 0x0020;
    public const uint BIF_NEWDIALOGSTYLE = 0x0040;
    public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX);
    public const uint BIF_BROWSEINCLUDEURLS = 0x0080;
    public const uint BIF_BROWSEFORCOMPUTER = 0x1000;
    public const uint BIF_BROWSEFORPRINTER = 0x2000;
    public const uint BIF_BROWSEINCLUDEFILES = 0x4000;
    public const uint BIF_SHAREABLE = 0x8000;

    public const uint SHGFI_ICON = 0x000000100; // get icon
    public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name
    public const uint SHGFI_TYPENAME = 0x000000400; // get type name
    public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes
    public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location
    public const uint SHGFI_EXETYPE = 0x000002000; // return exe type
    public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index
    public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon
    public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state
    public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes
    public const uint SHGFI_LARGEICON = 0x000000000; // get large icon
    public const uint SHGFI_SMALLICON = 0x000000001; // get small icon
    public const uint SHGFI_OPENICON = 0x000000002; // get open icon
    public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon
    public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl
    public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
    public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays
    public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay

    public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
    public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;

    [DllImport("Shell32.dll")]
    public static extern IntPtr SHGetFileInfo(
        string pszPath,
        uint dwFileAttributes,
        ref SHFILEINFO psfi,
        uint cbFileInfo,
        uint uFlags
        );

    #region Nested type: BROWSEINFO

    [StructLayout(LayoutKind.Sequential)]
    public struct BROWSEINFO
    {
        public IntPtr hwndOwner;
        public IntPtr pidlRoot;
        public IntPtr pszDisplayName;
        [MarshalAs(UnmanagedType.LPTStr)] public string lpszTitle;
        public uint ulFlags;
        public IntPtr lpfn;
        public int lParam;
        public IntPtr iImage;
    }

    #endregion

    #region Nested type: ITEMIDLIST

    [StructLayout(LayoutKind.Sequential)]
    public struct ITEMIDLIST
    {
        public SHITEMID mkid;
    }

    #endregion

    #region Nested type: SHFILEINFO

    [StructLayout(LayoutKind.Sequential)]
    public struct SHFILEINFO
    {
        public const int NAMESIZE = 80;
        public IntPtr hIcon;
        public int iIcon;
        public uint dwAttributes;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] public string szDisplayName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)] public string szTypeName;
    };

    #endregion

    #region Nested type: SHITEMID

    [StructLayout(LayoutKind.Sequential)]
    public struct SHITEMID
    {
        public ushort cb;
        [MarshalAs(UnmanagedType.LPArray)] public byte[] abID;
    }

    #endregion
}

/// <summary>
/// Wraps necessary functions imported from User32.dll. Code courtesy of MSDN Cold Rooster Consulting example.
/// </summary>
public class User32
{
    /// <summary>
    /// Provides access to function required to delete handle. This method is used internally
    /// and is not required to be called separately.
    /// </summary>
    /// <param name="hIcon">Pointer to icon handle.</param>
    /// <returns>N/A</returns>
    [DllImport("User32.dll")]
    public static extern int DestroyIcon(IntPtr hIcon);
}


Answer 2:

刚刚成功地得到的东西就是这样的工作。 当Visual Studio之外运行它此前被撞毁严重。 在此之前,我们会经常被找回的默认图标,而不是正确的文件类型(因为我们的文件图标后,未在目录图标)。

的因素汇总考虑。

  • 如已经讨论的,使用Shell相互作用需要使用STA螺纹,其泵的消息。 一个BackgroundWorker不会足够在这里。
  • 当你SHFILEINFO初始化, 设置这两个字符串属性(显示名称和类型名)的String.Empty。 这不是大多数样品中显示,但未能做到这一点是导致飞机坠毁的美国。 (在调试模式下,它意味着,我们得到的第一个图标后面是错误的,这可能是同你的问题。)
  • 检查互操作的声明是正确的。 例如, SHFILEINFO类应该大概有[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]属性。

一的TaskScheduler后台任务在STA线程

我们使用任务并行库,所以想要一个的TaskScheduler,将安排在合适的后台线程的工作。 下面的代码示例是用于暴露可以用于此一的TaskScheduler属性的类。

请注意,在我们的,我们有这个类,持续应用的整个生命周期的一个实例,所以我们没有实现IDisposable的。 如果你想创建/销毁这些你需要处理的。

namespace MyNamespace
{
    using System;
    using System.ComponentModel.Composition;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Threading;

    /// <summary>
    /// Exposes a <see cref="TaskScheduler"/> that schedules its work on a STA background thread.
    /// </summary>
    [Export]
    public class StaTaskSchedulerSource
    {
        /// <summary>
        /// A window that is used for message pumping.
        /// </summary>
        private Window window;

        /// <summary>
        /// Thread on which work is scheduled.
        /// </summary>
        private Thread thread;

        /// <summary>
        /// The <see cref="TaskScheduler"/> exposed by this class.
        /// </summary>
        private TaskScheduler taskScheduler;

        /// <summary>
        /// Initializes a new instance of the <see cref="StaTaskSchedulerSource"/> class.
        /// </summary>
        public StaTaskSchedulerSource()
        {
            using (ManualResetEvent re = new ManualResetEvent(false))
            {
                this.thread = new Thread(
                    () =>
                    {
                        this.window = new Window();

                        re.Set();

                        Dispatcher.Run();
                    });

                this.thread.IsBackground = true;
                this.thread.SetApartmentState(ApartmentState.STA);

                this.thread.Start();

                re.WaitOne();
            }

            this.window.Dispatcher.Invoke(
                new Action(
                    () =>
                    {
                        this.taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
                    }));
        }

        /// <summary>
        /// Gets a <see cref="TaskScheduler"/> that schedules work on a background STA
        /// thread.
        /// </summary>
        public TaskScheduler TaskScheduler
        {
            get
            {
                return this.taskScheduler;
            }
        }
    }
}

当然,这一切都只是包装用调度调用另一个线程的调用。 这可能是一个有点慢。 因此,如果处理许多图标,这将是更好批量起来。 此外,我们缓存我们已经获取的,所以我们并不需要周围使用赢壳牌在所有第二次的图标。

SafeIconHandle

您也可以找到一个图标下面的包装处理非常有用。 它派生自SafeHandle的 ,并确保您图标在所有情况下正确销毁。

namespace UnmanagedResourceLib
{
    using System;
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using Microsoft.Win32.SafeHandles;

    /// <summary>
    /// A <see cref="SafeHandle"/> implementation for HICON icon handles.
    /// </summary>
    [SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    internal class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        /// <summary>
        /// Prevents a default instance of the <see cref="SafeIconHandle"/> class from being created.
        /// </summary>
        private SafeIconHandle()
            : base(true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SafeIconHandle"/> class.
        /// </summary>
        /// <param name="nativeHandle">The HICON to wrap.</param>
        /// <param name="ownsHandle"><c>true</c> if finalization of this object should cause the icon to be destroyed.</param>
        public SafeIconHandle(IntPtr nativeHandle, bool ownsHandle)
            : base(ownsHandle)
        {
            this.handle = nativeHandle;
        }

        /// <inheritdoc />
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        override protected bool ReleaseHandle()
        {
            return NativeMethods.DestroyIcon(this.handle);
        }

        /// <summary>
        /// Exposes Windows API call to destroy an icon.
        /// </summary>
        [SuppressUnmanagedCodeSecurity]
        internal static class NativeMethods
        {
            /// <summary>
            /// Destroys an icon and frees any memory the icon occupied. 
            /// </summary>
            /// <param name="hIcon">A handle to the icon to be destroyed.</param>
            /// <returns><c>true</c> if the function succeeds.</returns>
            [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
            public static extern bool DestroyIcon(IntPtr hIcon);
        }
    }
}


文章来源: Calling SHGetFileInfo in thread to avoid UI freeze