我不能简单地将 CreateWindowEx 函数调用移至 taskFactory.StartNew() 范围内。
抱歉,但你必须这样做。尽管您可以将消息发送/发布到驻留在另一个线程中的窗口,但检索和分派消息不能跨线程边界工作。创建窗口、销毁该窗口以及为该窗口运行消息循环都必须在同一线程上下文中完成。
在您的情况下,这意味着所有逻辑都必须位于您传递给的回调内部taskFactory.StartNew()
.
关于如何实现这一目标有什么想法吗?也许从 GetMessage 更改为 PeekMessage(不过,第二个可能会使用大量 CPU)?
那并不能解决你的问题。两个都GetMessage()
and PeekMessage()
仅从调用线程的消息队列中拉取消息,并且该拉取只能返回调用线程拥有的窗口的窗口消息。他们的文档中明确指出了这一点:
获取消息 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage
hWnd
类型:HWND
要检索其消息的窗口的句柄。该窗口必须属于当前线程。
If hWnd
为 NULL,GetMessage 检索消息对于属于当前线程的任何窗口,以及当前线程消息队列上的任何消息hwnd
值为 NULL(请参阅MSG
结构)。因此如果hWnd
为NULL时,窗口消息和线程消息都会被处理。
如果hWnd为-1,则GetMessage仅检索当前线程消息队列上的消息hwnd
值为 NULL,即线程消息由PostMessage
(当。。。的时候hWnd
参数为 NULL) 或PostThreadMessage
.
窥视消息 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagew
hWnd
类型:HWND
要检索其消息的窗口的句柄。该窗口必须属于当前线程。
If hWnd
为 NULL,PeekMessage 检索消息属于当前线程的任何窗口,以及当前线程消息队列上的任何消息hwnd
值为 NULL(请参阅MSG
结构)。因此如果hWnd
为NULL时,窗口消息和线程消息都会被处理。
If hWnd
为-1时,PeekMessage仅检索当前线程消息队列上的消息,其hwnd
值为 NULL,即线程消息由PostMessage
(当。。。的时候hWnd
参数为 NULL) 或PostThreadMessage
.
The only之间的差异GetMessage()
and PeekMessage()
是:
现在,话虽如此,请尝试以下操作。它将保持与原始代码相同的语义,即它在退出之前等待创建新窗口。窗口创建仅在任务线程中执行,而不是在调用线程中执行:
public bool Start()
{
if (!Running)
{
Handle = IntPtr.Zero;
var readyEvent = new ManualResetEventSlim();
// Launch the message loop thread
taskFactory.StartNew(() =>
{
var processHandle = Process.GetCurrentProcess().Handle;
var windowClass = new WndClassEx
{
lpszMenuName = null,
hInstance = processHandle,
cbSize = WndClassEx.Size,
lpfnWndProc = WndProc,
lpszClassName = Guid.NewGuid().ToString()
};
// Register the dummy window class
var classAtom = RegisterClassEx(ref windowClass);
// Check whether the class was registered successfully
if (classAtom != 0u)
{
// Create the dummy window
Handle = CreateWindowEx(0x08000000, classAtom, "", 0, -1, -1, -1, -1, IntPtr.Zero, IntPtr.Zero, processHandle, IntPtr.Zero);
Running = Handle != IntPtr.Zero;
}
readyEvent.Set();
if (Handle != IntPtr.Zero)
{
Message message;
while (GetMessage(out message, IntPtr.Zero, 0, 0) != 0)
{
TranslateMessage(ref message);
DispatchMessage(ref message);
}
// if the message queue received WM_QUIT other than
// from the window being destroyed, for instance by
// a corresponding Stop() method posting WM_QUIT
// to the window, then destroy the window now...
if (IsWindow(Handle))
{
DestroyWindow(Handle);
}
Handle = IntPtr.Zero;
}
});
readyEvent.Wait();
}
return Running;
}