CoWaitForMultipleHandles API的行为不像logging

这是我看到的另一个问题引发的。 阅读可能太长,请耐心等待。

显然, CoWaitForMultipleHandles行为不像MSDN上logging的那样。

下面的代码(基于原始问题)是一个控制台应用程序,它启动一个testingWin32窗口的STA线程,并尝试发布和泵送一些消息。 它在CoWaitForMultipleHandles上做了三个不同的testing,都没有 COWAIT_WAITALL标志。

testing#1旨在validation这一点 :

COWAIT_INPUTAVAILABLE如果已设置,则即使input已经被查看(但未被删除),但对CoWaitForMultipleHandles的调用将返回S_OK,如果队列中存在input,则会调用另一个函数(例如PeekMessage)。

这不会发生, CoWaitForMultipleHandles阻塞,并不会返回,直到等待手柄信号。 我假设任何挂起的消息应该被视为input (与MWMO_INPUTAVAILABLEMsgWaitForMultipleObjectsEx相同,预计工作)。

testing#2旨在validation这一点 :

COWAIT_DISPATCH_WINDOW_MESSAGES允许从ASTA或STA中的CoWaitForMultipleHandles分派窗口消息。 在ASTA中默认是没有窗口消息派发,STA中的默认只是一小组调度的特殊的消息。 该值在MTA中没有意义,并被忽略。

这也不pipe用。 当CoWaitForMultipleHandles调用COWAIT_DISPATCH_WINDOW_MESSAGES时,会立即返回错误CO_E_NOT_SUPPORTED (0x80004021)。 如果是COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS的组合 COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS ,呼叫块但不会泵送任何消息。

testing#3演示了我可以使CoWaitForMultipleHandles泵送调用线程的Windows消息队列的唯一方法。 这是COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE的组合 COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE这确实泵和发送消息,虽然显然这是一个无证的行为。

testing代码 (一个准备运行的控制台应用程序):

 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消息...

 testing#1。  CoWaitForMultipleHandles只有COWAIT_INPUTAVAILABLE,按Enter停止...

结果:0,等待队列中的消息:True

 testing#2。  CoWaitForMultipleHandles与COWAIT_DISPATCH_WINDOW_MESSAGES |  COWAIT_DISPATCH_CALLS,按Enter停止...

结果:0,等待队列中的消息:True

 testing#3  CoWaitForMultipleHandles与COWAIT_DISPATCH_WINDOW_MESSAGES |  COWAIT_DISPATCH_CALLS |  COWAIT_INPUTAVAILABLE,按Enter停止...
 WM_TEST处理:1
 WM_TEST处理:2
 WM_TEST处理:3

结果:0,等待队列中的消息:False

 STA线程完成。
按Enter键退出。

所有的testing都是在Windows 8.1 Pro 64bit + NET v4.5.1下完成的。

  • 我误读了文档还是错过了其他的东西?

  • 我应该报告这是一个错误(至less,在文档中的错误)?

  • 应该避免使用CoWaitForMultipleHandles并用基于MsgWaitForMultipleObjectsEx (其行为符合文档)的解决schemereplace?

[更新]在Windows 7下, COWAIT_DISPATCH_WINDOW_MESSAGESCOWAIT_DISPATCH_CALLS被支持, CoWaitForMultipleHandles失败, E_INVALIDARG (0x80070057)。 当用零作为标志进行调用时,它不会被泵送。

CoWaitForMultipleHandles用于处理CoWaitForMultipleHandles中的COM窗口消息(例如,跨公寓编组)和其他一些(不要问我是哪个),或者简单地在MTA中阻塞。 在这篇博文中,克里斯·布鲁姆(Chris Brumme)的“托管阻塞”(Managed blocking)指出, CWFMH处理“恰到好处”的窗口消息。 但是,由于它会在队列中留下任何非COM发布的窗口消息,所以队列可能仍会填满,而不会使用COM窗口消息。

根据这份文件,“将Windows 8 Consumer Preview应用程序迁移到Windows 8 Release Preview” ,它说:

Windows Store应用程序不再支持CoWaitForMultipleHandles函数。 另外,以下CoWait_Flags已被删除:

COWAIT_DISPATCH_CALLS

COWAIT_DISPATCH_WINDOW_MESSAGES

如果你真的想处理所有的消息,你应该在带有GetMessage的消息循环中使用MsgWaitForMultipleObjectsEx ,或者在PM_REMOVE使用PeekMessage 。 这样做意味着潜在的狂潮狂潮。 您仍然不能控制从堆栈中的其他组件进一步调用STA。 也就是说,一个模式对话框(例如,公开对话框)可能会在简单的旧窗口消息循环中抽取每个消息,但是一些框架可能会调用CoWaitForMultipleHandles

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

这与例如冗长的UI调用(如OLE嵌入或模式对话框)不同,其中通常沿着堆栈有一个窗口消息循环。 或者从冗长而可碎的/可恢复的操作(例如一个状态机),在那里你可以通过稍微处理消息来进行协作,或者使用等待函数,当有消息时返回,所以你可以在再次等待之前处理它们。

小心,这只适用于一个手柄; 对于多个句柄(例如互斥体),您可能希望全部或全部为零,而下一个最佳方法是一个活动循环,对WaitForMultipleObjects进行超时调用,然后使用带有PM_REMOVE窗口消息循环的PeekMessage进行PM_REMOVE 。 这是一个边界线的情况,对于用户关注的中心UI应用程序来说是可以接受的(例如,这是他们的主要工作),但是如果这样的代码可以在无人值守和按需的情况下运行,这是不可接受的。 除非您确定需要在STA或UI线程中发生,否则我的建议是不要这样做。

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