CoWaitForMultipleHandles

CoWaitForMultipleHandles

这是由我正在查看的another question触发的。阅读时间可能太长,所以请多多包涵。

显然, CoWaitForMultipleHandles 的行为与MSDN上所记录的不同。

下面的代码(基于原始问题)是一个控制台应用程序,它将使用测试Win32窗口启动STA线程,并尝试发布并发送一些消息。它对CoWaitForMultipleHandles进行了三种不同的测试,所有测试都没有COWAIT_WAITALL标志。

测试#1 旨在验证this:



这不会发生,CoWaitForMultipleHandles会阻塞,并且直到发出等待句柄时才会返回。我确实假定任何待处理的邮件都应被视为输入(与MWMO_INPUTAVAILABLEMsgWaitForMultipleObjectsEx相同,它可以按预期工作)。

测试#2 旨在验证this:



这也不起作用。当仅用CoWaitForMultipleHandles标志调用COWAIT_DISPATCH_WINDOW_MESSAGES时,它立即返回错误CO_E_NOT_SUPPORTED(0x80004021)。如果是COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS的组合,则调用会阻止,但不会发送任何消息。

测试#3 演示了我可以让CoWaitForMultipleHandles泵送调用线程的Windows消息队列的唯一方法。它是COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE的组合。 尽管确实是未记录的行为,但它确实会发送和分发消息。

测试代码(现成的控制台应用程序):

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleTestApp
{
    static class Program
    {
        // Main
        static void Main(string[] args)
        {
            Console.WriteLine("Starting an STA thread...");
            RunStaThread();

            Console.WriteLine("\nSTA thread finished.");
            Console.WriteLine("Press Enter to exit.");
            Console.ReadLine();
        }

        // start and run an STA thread
        static void RunStaThread()
        {
            var thread = new Thread(() =>
            {
                // create a simple Win32 window
                IntPtr hwnd = CreateTestWindow();

                // Post some WM_TEST messages
                Console.WriteLine("Post some WM_TEST messages...");
                NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(1), IntPtr.Zero);
                NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(2), IntPtr.Zero);
                NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(3), IntPtr.Zero);

                // Test #1
                Console.WriteLine("\nTest #1. CoWaitForMultipleHandles with COWAIT_INPUTAVAILABLE only, press Enter to stop...");
                var task = ReadLineAsync();

                uint index;
                var result = NativeMethods.CoWaitForMultipleHandles(
                    NativeMethods.COWAIT_INPUTAVAILABLE,
                    NativeMethods.INFINITE,
                    1, new[] { task.AsUnmanagedHandle() },
                    out index);
                Console.WriteLine("Result: " + result + ", pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0));

                // Test #2
                Console.WriteLine("\nTest #2. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, press Enter to stop...");
                task = ReadLineAsync();

                result = NativeMethods.CoWaitForMultipleHandles(
                    NativeMethods.COWAIT_DISPATCH_WINDOW_MESSAGES |
                        NativeMethods.COWAIT_DISPATCH_CALLS,
                    NativeMethods.INFINITE,
                    1, new[] { task.AsUnmanagedHandle() },
                    out index);
                Console.WriteLine("Result: " + result + ", pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0));

                // Test #3
                Console.WriteLine("\nTest #3. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, press Enter to stop...");
                task = ReadLineAsync();

                result = NativeMethods.CoWaitForMultipleHandles(
                    NativeMethods.COWAIT_DISPATCH_WINDOW_MESSAGES |
                        NativeMethods.COWAIT_DISPATCH_CALLS |
                        NativeMethods.COWAIT_INPUTAVAILABLE,
                    NativeMethods.INFINITE,
                    1, new[] { task.AsUnmanagedHandle() },
                    out index);
                Console.WriteLine("Result: " + result + ", pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0));
            });

            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();

            thread.Join();
        }

        //
        // Helpers
        //

        // create a window to handle messages
        static IntPtr CreateTestWindow()
        {
            // Create a simple Win32 window
            var hwndStatic = NativeMethods.CreateWindowEx(0, "Static", String.Empty, NativeMethods.WS_POPUP,
                0, 0, 0, 0, NativeMethods.HWND_MESSAGE, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

            // subclass it with a custom WndProc
            IntPtr prevWndProc = IntPtr.Zero;

            NativeMethods.WndProc newWndProc = (hwnd, msg, wParam, lParam) =>
            {
                if (msg == NativeMethods.WM_TEST)
                    Console.WriteLine("WM_TEST processed: " + wParam);
                return NativeMethods.CallWindowProc(prevWndProc, hwnd, msg, wParam, lParam);
            };

            prevWndProc = NativeMethods.SetWindowLong(hwndStatic, NativeMethods.GWL_WNDPROC,
                Marshal.GetFunctionPointerForDelegate(newWndProc));
            if (prevWndProc == IntPtr.Zero)
                throw new ApplicationException();

            return hwndStatic;
        }

        // call Console.ReadLine on a pool thread
        static Task<string> ReadLineAsync()
        {
            return Task.Run(() => Console.ReadLine());
        }

        // get Win32 waitable handle of Task object
        static IntPtr AsUnmanagedHandle(this Task task)
        {
            return ((IAsyncResult)task).AsyncWaitHandle.SafeWaitHandle.DangerousGetHandle();
        }
    }

    // Interop
    static class NativeMethods
    {
        [DllImport("user32")]
        public static extern IntPtr SetWindowLong(IntPtr hwnd, int nIndex, IntPtr dwNewLong);

        [DllImport("user32")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr CreateWindowEx(
            uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle,
            int x, int y, int nWidth, int nHeight,
            IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);

        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern int MessageBox(IntPtr hwnd, string text, String caption, int options);

        [DllImport("ole32.dll", SetLastError = true)]
        public static extern uint CoWaitForMultipleHandles(uint dwFlags, uint dwTimeout,
           int cHandles, IntPtr[] pHandles, out uint lpdwindex);

        [DllImport("user32.dll")]
        public static extern uint GetQueueStatus(uint flags);

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate IntPtr WndProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        public static IntPtr HWND_MESSAGE = new IntPtr(-3);

        public const int GWL_WNDPROC = -4;
        public const uint WS_POPUP = 0x80000000;

        public const uint WM_USER = 0x0400;
        public const uint WM_TEST = WM_USER + 1;

        public const uint COWAIT_WAITALL = 1;
        public const uint COWAIT_ALERTABLE = 2;
        public const uint COWAIT_INPUTAVAILABLE = 4;
        public const uint COWAIT_DISPATCH_CALLS = 8;
        public const uint COWAIT_DISPATCH_WINDOW_MESSAGES = 0x10;

        public const uint RPC_S_CALLPENDING = 0x80010115;

        public const uint WAIT_TIMEOUT = 0x00000102;
        public const uint WAIT_FAILED = 0xFFFFFFFF;
        public const uint WAIT_OBJECT_0 = 0;
        public const uint WAIT_ABANDONED_0 = 0x00000080;
        public const uint WAIT_IO_COMPLETION = 0x000000C0;

        public const uint INFINITE = 0xFFFFFFFF;
    }
}

输出:

正在启动STA线程...
发布一些WM_TEST消息...

测试#1。仅使用COWAIT_INPUTAVAILABLE的CoWaitForMultipleHandles,按Enter即可停止...

结果:0,队列中的未决消息:True

测试#2。具有COWAIT_DISPATCH_WINDOW_MESSAGES的CoWaitForMultipleHandles | COWAIT_DISPATCH_CALLS,按Enter即可停止...

结果:0,队列中的未决消息:True

测试#3。具有COWAIT_DISPATCH_WINDOW_MESSAGES的CoWaitForMultipleHandles | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE,按Enter键即可停止...
WM_TEST已处理:1
WM_TEST已处理:2
WM_TEST已处理:3

结果:0,队列中的未决消息:False

STA线程完成。
按Enter退出。

所有测试均在Windows 8.1 Pro 64位+ NET v4.5.1下完成。
  • 我误读文档或遗漏了其他东西吗?
  • 我是否应该将此报告为错误(至少是文档中的错误)?
  • 是否应该避免CoWaitForMultipleHandles并替换为基于 MsgWaitForMultipleObjectsEx 的解决方案(其行为符合文档)?

  • [更新] 在Windows 7下,不支持COWAIT_DISPATCH_WINDOW_MESSAGESCOWAIT_DISPATCH_CALLSCoWaitForMultipleHandles失败,显示E_INVALIDARG(0x80070057)。当以零作为标志调用时,它将阻塞而不进行泵送。

    最佳答案

    CoWaitForMultipleHandles用于处理STA中的COM窗口消息(例如,跨部门编码)和其他一些消息(不要问我哪个),或者只是在MTA中进行阻止。在this blog post, «Managed blocking» by Chris Brumme中,它说CWFMH处理“适量”的窗口消息。但是,由于它在队列中留下了任何非COM发布的窗口消息,因此该队列可能仍会填满,只是不包含COM窗口消息。

    根据this document, «Migrating your Windows 8 Consumer Preview app to Windows 8 Release Preview»,它说:



    如果您确实要处理所有消息,则应在消息循环中将MsgWaitForMultipleObjectsExGetMessage一起使用,或者将PeekMessagePM_REMOVE一起使用。这样做意味着潜在的折返狂潮。您仍然无法控制堆栈中其他组件对STA的进一步调用。也就是说,模式对话框(例如,用于打开的通用对话框)可能会在普通的窗口消息循环中泵送所有消息,但是某些框架可能会调用CoWaitForMultipleHandles

    最重要的是,如果您要进行密集的处理或阻塞操作,请将其委派给另一个线程(可能使用队列),并在需要时告诉调用UI线程在操作完成后进行更新。

    这与例如冗长的UI调用,例如OLE嵌入或模式对话框,其中通常在堆栈的某处存在窗口消息循环。或者通过冗长但可分割的/可恢复的操作(例如状态机),您可以通过偶尔处理一次消息或使用有消息时返回的等待函数进行协作,以便可以在再次等待之前对其进行处理。

    请注意,这仅适用于一个 handle 。用于多个 handle ,例如互斥锁,您要么全部要么不想要,而下一个最佳方法是主动循环,先超时调用WaitForMultipleObjects,然后再调用带有PeekMessage窗口消息循环的PM_REMOVE。这是一个边界情况,对于作为用户关注中心的UI应用程序是可接受的(例如,这是他们的主要工作),但是如果此类代码可以在无人看管且按需运行的情况下是 Not Acceptable 。除非您确定它需要在STA或UI线程中发生,否则我的建议是不要这样做。

    最后,您可能应该在Microsoft Connect上打开一个错误,至少要更新文档。或实际上使它像“预期的”那样工作。

    关于c# - CoWaitForMultipleHandles API的行为与所记录的不一样,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21226600/

    10-11 20:54