这是由another question我所看到的。阅读时间可能太长,所以请耐心等待。
显然,CoWaitForMultipleHandles
不的行为与MSDN上记录的相同。
下面的代码(基于原始问题)是一个控制台应用程序,它启动一个带有测试Win32窗口的STA线程并尝试发布并抽取一些消息。它对CoWaitForMultipleHandles
进行了三次不同的测试,所有没有 COWAIT_WAITALL
标记。
测试#1 旨在验证this:
COWAIT_INPUTAVAILABLE 如果设置,则调用CoWaitForMultipleHandles 如果队列存在输入,则返回S_OK,即使输入有 已经看到(但没有删除)使用另一个函数的调用,如 的PeekMessage。
这没有发生,CoWaitForMultipleHandles
阻塞,直到等待句柄发出信号才返回。我确实假设任何待处理的消息都应被视为输入(与MWMO_INPUTAVAILABLE
的{{1}}相同,其效果与预期一致。)
测试#2 旨在验证this:
COWAIT_DISPATCH_WINDOW_MESSAGES 启用窗口消息的分派 来自ASTA或STA中的CoWaitForMultipleHandles。 ASTA中的默认值为no 调度窗口消息,默认在STA中只是一小部分 发送特殊信息。该值在MTA中没有意义 被忽略了。
这也不起作用。仅使用MsgWaitForMultipleObjectsEx
标志调用CoWaitForMultipleHandles
时,它会立即返回错误COWAIT_DISPATCH_WINDOW_MESSAGES
(0x80004021)。如果它是CO_E_NOT_SUPPORTED
的组合,则呼叫阻止,但不会提取任何消息。
测试#3 演示了我可以使COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS
泵送调用线程的Windows消息队列的唯一方法。它是CoWaitForMultipleHandles
的组合。 这会发送和发送消息,但显然这是一种无证件行为。
测试代码(可立即运行的控制台应用):
COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE
输出:
Starting an STA thread... Post some WM_TEST messages... Test #1. CoWaitForMultipleHandles with COWAIT_INPUTAVAILABLE only, press Enter to stop... Result: 0, pending messages in the queue: True Test #2. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, press Enter to stop... Result: 0, pending messages in the queue: True Test #3. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, press Enter to stop... WM_TEST processed: 1 WM_TEST processed: 2 WM_TEST processed: 3 Result: 0, pending messages in the queue: False STA thread finished. Press Enter to exit.
所有测试均在Windows 8.1 Pro 64bit + NET v4.5.1下完成。
我误读了文档或遗漏了其他内容吗?
我应该将此报告为错误(至少是文档中的错误)吗?
是否应该避免使用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;
}
}
替换为基于MsgWaitForMultipleObjectsEx
的解决方案(根据文档行事)?
[更新] 在Windows 7下,CoWaitForMultipleHandles
和COWAIT_DISPATCH_WINDOW_MESSAGES
均未受支持,COWAIT_DISPATCH_CALLS
因CoWaitForMultipleHandles
(0x80070057)而失败。当以零作为标志调用时,它会在没有泵送的情况下阻塞
答案 0 :(得分:3)
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»,它说:
Windows应用商店应用不再支持CoWaitForMultipleHandles功能。此外,还删除了以下CoWait_Flags:
COWAIT_DISPATCH_CALLS
COWAIT_DISPATCH_WINDOW_MESSAGES
如果您确实要处理所有邮件,则应在MsgWaitForMultipleObjectsEx
或GetMessage
与PeekMessage
的邮件循环中使用PM_REMOVE
。这样做意味着潜在的重入狂潮。您仍然无法控制从堆栈中的其他组件进一步调用STA。也就是说,模式对话框(例如,用于打开的公共对话框)可能会在一个普通的窗口消息循环中抽取每条消息,但某些框架可能会调用CoWaitForMultipleHandles
。
底线是,如果您正在进行密集处理或阻塞操作,请将其委托给另一个线程(可能使用队列),如果需要,请在操作完成后告知调用UI线程更新。
这与例如不同冗长的UI调用,例如OLE嵌入或模式对话,其中通常在堆栈的某处存在窗口消息循环。或者通过冗长但可重复/可恢复的操作(例如状态机),您可以通过偶尔处理消息来协作,或者使用在有消息时返回的等待函数,以便您可以在再次等待之前处理它们。
小心,这只适用于一个手柄;多个手柄,例如互斥体,您需要全部或全部,并且下一个最佳方法是一个活动循环,对WaitForMultipleObjects
的超时调用后跟PeekMessage
PM_REMOVE
窗口消息循环。这是一个边界线的情况,对于作为用户注意力中心的UI应用程序是可接受的(例如,这是他们的主要工作),但如果这样的代码可以无人值守和按需运行,则是不可接受的。除非你确定它需要在STA或UI线程中发生,否则我的建议是不要这样做。
最后,您应该在Microsoft Connect上打开一个错误,至少要更新文档。或者实际上让它像预期的那样工作。