[英]Is it possible for a thread that is not the UI thread to manipulate the UI elements?
我已经读过,只允许UI线程操纵WinAPI中的UI元素。 但我认为,不是UI线程的线程甚至可能操纵UI元素。
我认为,因为当一个线程(不是UI线程)调用SendMessage()函数来操作某个UI元素时,会将一条消息发送到UI线程,然后它就是操作UI元素的UI线程而不是其他线程。
我对么?
首先,假设是为了满足OP的好奇心:
AttachThreadInput
指定另一个处理输入的线程。 但是,考虑到我们坚持使用标准Windows API来创建和管理UI的问题的前提,可以肯定地说,只有在创建它的线程内才能访问窗口,因为SendMessage()
将切换到所有者线程。 但这并不是说从多个线程调用SendMessage()
是一种安全或推荐的方法。 相反,它充满了危险,必须小心谨慎地使线程同步。
首先,典型的WndProc()
看起来像这样:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
switch (message)
{
case WM_MYMSG1:
...
SendMessage(hWnd, WM_MYMSG2, wParam, lParam);
...
break;
...
}
...
}
因此,为了保护您的WndProc()
以便可以从多个线程访问它,您必须确保使用可重入锁,而不是信号量。
其次,如果您使用可重入锁定,则必须确保它仅在WndProc()
或者甚至使其特定于消息。 否则很容易陷入僵局:
//Worker thread:
void foo ()
{
EnterCriticalSection(&g_cs);
SendMessage(hWnd, WM_MYMSG1, NULL, NULL);
LeaveCriticalSection(&g_cs);
}
//Owner thread:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_MYMSG1:
{
EnterCriticalSection(&g_cs); //Deadlock!
...
LeaveCriticalSection(&g_cs);
}
break;
}
}
第三,你必须确保不要在你的WndProc()
调用任何控制让你的函数; 这些包括但不限于 : DialogBox()
, MessageBox()
和GetMessage()
。 否则你会陷入僵局。
然后,考虑一个多窗口应用程序,每个窗口的消息泵在一个单独的线程中运行。 您必须确保不在线程之间发送任何消息,以免最终导致死锁:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
switch (message)
{
case WM_MYMSG1:
...
SendMessage(hWnd2, WM_MYMSG1, wParam, lParam); //Deadlock!
...
break;
...
}
...
}
您还必须非常小心地使用Windows API来隐式管理操作系统的特定于进程的锁,并保留和维护正确的锁层次结构 。 相当多的User32函数和许多阻塞COM调用都属于这一类。
通过使用InSendMessage()
和ReplyMessage()
(使用SendMessage()
)或PostMessage()
及其兄弟,可以缓解其中一些问题。 但是,您会遇到各种控制流问题,因为您可能想要在继续当前线程或处理下一条消息之前知道消息已被处理。 因此,您最终还是必须实现某种同步机制,但这会变得越来越困难,需要避免许多陷阱。
只是在线程之间发送消息也不会停止问题。 从另一个线程更改WndProc()
可能会导致可怕的竞争条件错误 :
//in UI thread:
wpOld = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
//in another thread:
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)otherWndProc);
//back in UI thread:
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newWndProc);
//still in UI thread:
LRESULT CALLBACK newWndProc(...)
{
CallWindowProc(wpOld, ...); //Wrong wpOld!
}
此外,不正确地使用来自多个线程的DC可能会导致细微的错误 。
这些原因以及其他原因(包括性能)可能导致标准API包装器(如MFC和WinForms)的设计者简单地假设他们的API将在单线程上下文中使用。 它们不提供任何线程安全保护,并且由用户来实现这样的机制,但是更高级别的抽象使得忽略潜在问题 更容易 。 当出现这样的问题时,通常答案是:不要使用所有者线程外部的控件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.