是不是UI线程的线程可以操作UI元素?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了是不是UI线程的线程可以操作UI元素?相关的知识,希望对你有一定的参考价值。

我已经读过,只允许UI线程操纵WinAPI中的UI元素。但我认为,不是UI线程的线程甚至可能操纵UI元素。

我认为,因为当一个线程(不是UI线程)调用SendMessage()函数来操作一些UI元素时,一条消息将被发送到UI线程,然后它是将操纵UI元素的UI线程而不是另一个线程。

我对么?

答案

首先,假设是为了满足OP的好奇心:

  • 如果我们将UI元素的操作定义为读取或写入元素的属性,那么从技术上讲,您可以提出自己的UI框架,该框架将独立于Windows API维护元素。这样的尝试have been made。 WPF就是其中之一。然后,理论上可以使框架成为线程安全的,并且可以从多个线程访问元素的属性。
  • 此外,GDI允许从多个线程访问其对象,因此您可能从多个线程(同样为DirectX)绘制到您的窗口。例如,WPF有一个专用的渲染线程(或者至少它曾经用过)。您还可以使用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()中调用任何控制让你的函数;这些include but are not limited toDialogBox()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,以及preserve and maintain the proper lock hierarchy。相当多的User32函数和许多阻塞COM调用都属于这一类。

使用InSendMessage()ReplyMessage()(当使用SendMessage())或PostMessage()及其兄弟姐妹时,可以减轻其中一些问题。但是,您会遇到各种控制流问题,因为您可能想要在继续​​当前线程或处理下一条消息之前知道消息已被处理。因此,您最终还是必须实现某种同步机制,但这会变得越来越困难,需要避免许多陷阱。

只是在线程之间发送消息也不会停止问题。从不同的线程更改WndProc()可以导致terrible race-condition bugs

//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可以lead to subtle bugs

这些原因以及其他原因(包括性能)可能导致标准API包装器(如MFC和WinForms)的设计者简单地假设他们的API将在单线程上下文中使用。它们不提供任何线程安全保护,并且由用户来实现这样的机制,然而更高的抽象级别使它成为even easierneglect the underlying issues。当出现这样的问题时,通常答案是:不要使用所有者线程外部的控件。

以上是关于是不是UI线程的线程可以操作UI元素?的主要内容,如果未能解决你的问题,请参考以下文章

多线程更新UI的常用方法

如何知道这个线程是不是是 UI 线程

检测主线程外的 UI 操作

您可以从另一个线程访问 UI 元素吗? (不设置)

主线程中也不绝对安全的 UI 操作

为啥loop之后就可以子线程更新ui