UI线程和工作者线程

UI与工作者线程解析
1、线程分为UI线程和工作者线程,UI线程有窗口,窗口自建了消息队列,这个UI线程维护“消息队列”,“消息队列”是界面线程和工作者线程的最大区别。所以有用户界面的一般称为UI线程,没有界面的称之为工作者线程,UI线程因为有界面,所以系统会给它维护一个消息队列,工作者线程就没有消息队列。

2、工作者线程原本是没有消息队列,但是你可以强制加一个,一般只要你的线程中出现了GDI的调用就会出现一个消息队列,线程中如果调用了GetMessage(),就可以强制加入了一个消息循环,系统就会给该线程加一个消息队列,同样用PeekMessage()也可以强制系统加入一个消息队列.工作者线程的消息传递是通过PostThreadMessage函数发送的。即:工作线程函数里面如果调用了有关消息的函数,操作系统自动为工作线成创建消息队列。


3、所谓的UI线程有窗口,窗口自建了消息队列。所谓的工作者线程初始状态没有自建消息队列。


4、UI线程消息处理过程

只有在使用MFC框架时才有UI线程和工作者线程之分。UI线程与工作者线程的区别是操作系统为UI线程创建并维护了一个消息队列。
其实线程在创建时(无论是API还是MFC),都是工作者线程。当线程调用发送消息或提取消息或图形用户界面相关的函数时,系统才为其创建一个消息队列和THREADINFO结构,这时的线程才称为UI线程。
VC开发的控制台程序的主线程是工作线程,其他程序的主线程为UI线程。_beginthreadex/CreateThread等函数创建的线程默认为工作线程,AfxBeginThread可以根据参数创建工作者线程和UI线程。
 
(1) 从用户输入到系统消息队列
操作系统会监视计算机的键盘和鼠标等输入设备,为每一个输入事件生成一个消息,将消息统一临时放到“系统消息队列”内。其中消息的窗口句柄由系统根据鼠标或光标所在区域计算出的。
 
(2) 从系统消息队列到线程消息队列
系统有专门的线程负责从系统消息队列中取出消息,根据消息的目标对象(窗口句柄),将消息投递到创建它的UI线程对应的消息队列中。每个UI线程有且仅有一个消息队列。
 
(3 )UI线程处理消息
UI线程启动一个消息循环(Message Loop),每次从本线程所对应的消息队列中取出一条消息,然后根据消息所包容的信息,将其转发给特定的窗体对象,此窗体对象所对应的“窗体过程”函数被调用以处理这些消息。
    MSG msg; // 代表一条消息
    BOOL bRet;
    // 从UI线程消息队列中取出一条消息
    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    {
        if (bRet == -1)
        {
            // 错误处理代码,通常是直接退出程序
        }
        else
        {
            TranslateMessage(&msg); // 转换消息格式
            DispatchMessage(&msg); // 分发消息给相应的窗体
        }
    }
 
GetMessage()等到有消息才返回,peekmessage()则马上返回消息或空值;GetMessage()获得消息后从队列中删除(WM_PAINT除外),peekmessage()则根据参数PM_NOREMOVE或PM_REMOVE来决定要不要删除。
TranslateMessage()函数主要用于将WM_KEYDOWN和WM_KEYUP消息转换WM_CHAR消息。所以如果要截获WM_KEYDOWN和WM_KEYUP消息,需要重载窗体类的PreTranslateMessage()函数进行分析。
DispatchMessage()函数根据取出的消息中所包含的窗体句柄,将这一消息转发给引此句柄所对应的窗体对象。负责响应消息的函数称为窗体过程(Window Procedure)。如果需要自定义处理,则重载窗体类的DefWindowProc()函数。窗体过程是一个函数,每个窗体一个,形式如下:
LRESULT CALLBACK MainWndProc(……)
    {
        //……
        switch (uMsg) // 依据消息标识符进行分类处理
        {
            case WM_CREATE:
                // 初始化窗体.
                return 0;
            case WM_PAINT:
                // 绘制窗体
                return 0;
            //
            // 处理其他消息
            //
            default:
                // 转去调用系统默认的消息处理函数
                return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
        //……
    }

<think> 我们正在讨论MFC中的线程创建。根据引用内容,工作者线程UI线程在创建方式上有明显区别。以下是详细分析: ### 1. **创建函数签名不同** - **工作者线程**:使用`AfxBeginThread`的第一个重载版本,参数为线程函数地址传递给该函数的参数: ```cpp CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, // 线程函数指针 LPVOID pParam, // 传递给线程函数的参数 int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); ``` 示例: ```cpp CWinThread* pWorker = AfxBeginThread(WorkerFunction, pData); ``` - **UI线程**:使用`AfxBeginThread`的第二个重载版本,参数为`CRuntimeClass*`(通常是派生自`CWinThread`的类): ```cpp CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, // 指向CWinThread派生类的运行时类信息 int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); ``` 示例: ```cpp CWinThread* pUI = AfxBeginThread(RUNTIME_CLASS(CMyUIThread)); ``` ### 2. **实现方式不同** - **工作者线程**: - 只需定义一个全局函数或静态成员函数作为线程入口点。 - 函数签名:`UINT __cdecl MyWorkerFunction(LPVOID pParam);` - 在该函数内执行后台任务(如计算、文件操作),**不能直接操作UI**。 - **UI线程**: - 需从`CWinThread`派生一个新类(如`CMyUIThread`)。 - 重写`InitInstance()`以初始化线程(通常创建窗口或对话框)。 - 重写`ExitInstance()`进行清理。 - MFC自动为该线程创建消息循环(通过`CWinThread::Run()`)。 ### 3. **消息队列差异** - **工作者线程**: - 默认**无消息队列**(除非调用GDI函数或手动创建窗口)[^5]。 - 若需处理消息,需显式实现消息循环(较少见)。 - **UI线程**: - 自动拥有**消息队列**消息循环(由`CWinThread::Run()`驱动)[^1][^4]。 - 可响应窗口消息(如按钮点击、定时器事件)。 ### 4. **代码示例对比** #### 工作者线程创建 ```cpp // 1. 定义线程函数 UINT WorkerProc(LPVOID pParam) { // 执行后台任务(非UI操作) return 0; } // 2. 创建线程 CWinThread* pWorker = AfxBeginThread(WorkerProc, NULL); ``` #### UI线程创建 ```cpp // 1. 派生CWinThread子类 class CMyUIThread : public CWinThread { DECLARE_DYNCREATE(CMyUIThread) public: virtual BOOL InitInstance() { // 创建窗口(如对话框) m_pMainWnd = new CMyDialog; m_pMainWnd->ShowWindow(SW_SHOW); return TRUE; } virtual int ExitInstance() { // 清理资源 return CWinThread::ExitInstance(); } }; IMPLEMENT_DYNCREATE(CMyUIThread, CWinThread) // 2. 创建线程 CWinThread* pUI = AfxBeginThread(RUNTIME_CLASS(CMyUIThread)); ``` ### 5. **应用场景** - **工作者线程**:适合后台计算、文件读写、网络通信等**无界面**任务。 - **UI线程**:适合运行独立窗口(如工具窗口、监控面板),需**直接处理用户交互**。 ### 总结 | **特性** | **工作者线程** | **UI线程** | |----------------|-----------------------------------|-------------------------------------| | **创建函数** | `AfxBeginThread(线程函数)` | `AfxBeginThread(CRuntimeClass*)` | | **入口点** | 全局函数或静态成员函数 | 派生类的`InitInstance()` | | **消息队列** | 默认无(可手动添加) | 自动创建并维护 | | **UI操作能力** | 禁止直接操作(需跨线程通信) | 可直接创建/操作窗口 | | **典型用途** | 耗时后台任务 | 独立窗口界面 | > ⚠️ 注意:UI线程虽可操作窗口,但MFC中仍推荐在主UI线程(通常是主应用程序线程)中管理主窗口,避免多窗口同步问题。 §§相关问题§§ 1. 如何在工作者线程中安全更新UI控件? 2. 如何手动为工作者线程添加消息循环? 3. UI线程的消息循环是如何实现的? 4. 为什么UI线程需要重写`InitInstance()`方法? </think> 在 MFC 框架中,工作者线程 UI 线程的创建存在显著区别,主要体现在创建方式、消息机制功能定位上。以下是详细对比分析: --- ### 1. **创建方式不同** #### **工作者线程**(Worker Thread) - **使用函数指针创建**: 调用 `AfxBeginThread` 的第一个重载版本,传入线程函数地址参数: ```cpp CWinThread* pWorker = AfxBeginThread( WorkerFunction, // 线程函数(全局函数或静态成员函数) pParam, // 传递给线程函数的参数 THREAD_PRIORITY_NORMAL ); ``` - **线程函数要求**: 需定义签名为 `UINT __cdecl MyWorkerFunction(LPVOID pParam)` 的函数,函数内执行后台任务(如计算、文件操作)[^3][^4]。 #### **UI 线程**(UI Thread) - **使用运行时类创建**: 调用 `AfxBeginThread` 的第二个重载版本,传入派生自 `CWinThread` 的类信息: ```cpp CWinThread* pUI = AfxBeginThread( RUNTIME_CLASS(CMyUIThread), // 派生类的运行时类信息 THREAD_PRIORITY_NORMAL ); ``` - **需派生自定义类**: 需从 `CWinThread` 派生新类(如 `CMyUIThread`),并重写 `InitInstance()` `ExitInstance()` 方法[^2][^3][^4]。 --- ### 2. **消息机制不同** | **特性** | **工作者线程** | **UI 线程** | |------------------|-----------------------------------|--------------------------------------| | **消息队列** | 默认无消息队列 | 自动创建并维护消息队列 | | **消息循环** | 无(除非主动实现) | 通过 `CWinThread::Run()` 自动实现 | | **消息处理能力** | 无法直接响应窗口消息 | 可处理窗口消息(如按钮点击、绘制) | - **UI 线程的消息循环**: 在 `CWinThread::Run()` 中自动实现经典消息泵: ```cpp while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); // 分发消息到窗口过程 } ``` 这使得 UI 线程能直接操作窗口控件[^4][^5]。 --- ### 3. **功能定位不同** | **特性** | **工作者线程** | **UI 线程** | |------------------|-----------------------------------|--------------------------------------| | **核心用途** | 后台任务(计算、I/O 操作) | 用户界面交互 | | **UI 操作权限** | 禁止直接操作 UI(需跨线程通信) | 可直接创建/操作窗口 | | **典型场景** | 文件压缩、网络通信 | 独立对话框、工具窗口 | > 💡 **关键区别**: > UI 线程因拥有消息队列窗口,能直接处理用户交互;工作者线程无消息队列,仅适合纯后台任务[^1][^3][^5]。 --- ### 4. **代码结构对比** #### **工作者线程示例** ```cpp // 1. 定义线程函数 UINT FileProcessingThread(LPVOID pParam) { // 执行文件操作(无UI调用) return 0; } // 2. 创建线程 AfxBeginThread(FileProcessingThread, pFileData); ``` #### **UI 线程示例** ```cpp // 1. 派生CWinThread子类 class CMyDialogThread : public CWinThread { DECLARE_DYNCREATE(CMyDialogThread) public: virtual BOOL InitInstance() { m_pMainWnd = new CMyDialog; // 创建对话框 m_pMainWnd->ShowWindow(SW_SHOW); return TRUE; } }; IMPLEMENT_DYNCREATE(CMyDialogThread, CWinThread) // 2. 创建线程 AfxBeginThread(RUNTIME_CLASS(CMyDialogThread)); ``` --- ### 总结 | **维度** | **工作者线程** | **UI 线程** | |----------------|-----------------------------------|--------------------------------------| | **创建方式** | 函数指针 | 运行时类(派生自 `CWinThread`) | | **消息机制** | 无默认消息队列 | 自动消息队列循环 | | **功能定位** | 后台任务 | 用户界面交互 | | **UI 操作** | 禁止直接操作 | 可直接操作 | > ⚠️ **重要提示**: > - 工作者线程中调用 GDI 函数可能隐式创建消息队列,但这是未定义行为,应避免[^5]。 > - UI 线程需手动管理窗口资源(在 `ExitInstance()` 中销毁窗口)[^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值