C ++从异步线程更新Windows窗口
Posted
技术标签:
【中文标题】C ++从异步线程更新Windows窗口【英文标题】:C++ updating windows window from async thread 【发布时间】:2017-02-18 15:56:04 【问题描述】:所以我刚开始使用 C++ 并想创建一个带有按钮的窗口,该按钮为从 5 计数到 0 的计数器启动异步线程,这代表了一项耗时的任务。该数字应该已显示在窗口上,并在计数器计数时每秒更新一次。为此,子线程必须以任何方式与主窗口线程的消息循环进行通信。 我试图通过以下方式做到这一点:
使用主窗口的窗口句柄发送 UpdateWindow 使用主窗口的窗口句柄发送 PostMessage但是在这两种情况下,窗口都没有得到更新。因此,我怀疑通过将窗口句柄从主线程发送到子线程或将 UpdateWindow 消息从子线程发送到主线程或两者都发生错误,或者我完全偏离轨道并且一切都是错误的。
也许我的思维方式也是错误的,我应该换一种方式去做,但我什至不知道我应该如何开始。
#include "stdafx.h"
#include "Testproject.h"
#include <iostream>
#include <string>
#include <thread>
#define MAX_LOADSTRING 100
// Global variables:
HINSTANCE hInst; // Aktuelle Instanz
WCHAR szTitle[MAX_LOADSTRING]; // Titelleistentext
WCHAR szWindowClass[MAX_LOADSTRING];
HWND Button1;
int i = 0;
我的柜台:
void counterr(HWND hWnd)
i = 5;
while(i>0)
i -= 1;
//UpdateWindow(hWnd);
PostMessage(hWnd, WM_PRINT, NULL, NULL);
Sleep(1000);
来自 VisualStudio2017 的标准窗口和消息循环内容
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
switch (message)
case WM_CREATE:
Button1 = CreateWindow(L"Button",L"Counter",WS_VISIBLE|WS_CHILD|WS_BORDER,0,40,100,20,hWnd,(HMENU) 1,nullptr,nullptr);
break;
case WM_COMMAND:
int wmId = LOWORD(wParam);
// Menüauswahl bearbeiten:
switch (wmId)
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
case 1:
std::thread t1(counterr, hWnd);
t1.detach();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
case WM_PRINT:
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
//TODO: Zeichencode, der hdc verwendet, hier einfügen...
RECT rc;
RECT rc2 = 0, 0, 0, 0 ;
int spacer = 3;
GetClientRect(hWnd, &rc);
SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(0, 0, 0));
std::wstring strOut = std::to_wstring(i); // or wstring if you have unicode set
DrawText(hdc, strOut.c_str(), strOut.length(), &rc, DT_SINGLELINE);
DrawText(hdc, strOut.c_str(), strOut.length(), &rc2, DT_CALCRECT);
rc.left = rc.left + rc2.right + spacer;
std::wstring strOut2 = L"heya";
DrawText(hdc, strOut2.c_str(), strOut2.length(), &rc, DT_TOP | DT_LEFT | DT_SINGLELINE);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
return 0;
又是标准的东西,代码结束
【问题讨论】:
您不应该从其他线程更新 UI 线程。它将具有未定义的行为 那我该怎么做呢? 这个帖子会回答你的问题:***.com/questions/3783713/… 感谢您的回答,但这不符合我的问题。我不想将数据发送到我的 UI 线程(现在)。我想从后台线程向 UI 线程发送“嘿,更新窗口,UI 线程”,这不起作用,即使使用链接中的解决方案,因为有问题,被我误解了。 【参考方案1】:执行此操作的常用方法是使用 自定义消息 ID 调用 SendMessage() 或 PostMessage() 以通知 UI 线程所做的一些更改。
直接从线程更新 UI 是不好的做法,因为线程应该只做“工作”,而不关心 UI 将如何呈现这项工作的结果。
您已经通过使用 PostMessage 走上了正确的道路。但是,您应该像这样定义自定义消息 ID,而不是使用 WM_PRINT:
const UINT WM_APP_MY_THREAD_UPDATE = WM_APP + 0;
WM_APP 到 0xBFFF 范围内的消息保留供应用程序私人使用,因此您不必担心某些 Windows 组件已使用您的消息 ID。
然后你的线程函数调用:
PostMessage(hWnd, WM_APP_MY_THREAD_UPDATE, 0, 0);
在您的 WndProc 中,将 case WM_PRINT:
替换为:
case WM_APP_MY_THREAD_UPDATE:
// Tell Windows that the window content is no longer valid and
// it should update it as soon as possible.
// If you want to improve performance a little bit, pass a rectangle
// to InvalidateRect() that defines where the number is painted.
InvalidateRect( hWnd, nullptr, TRUE );
break;
您的代码还有另一个问题:
您的counterr
线程函数更新全局变量i
而不考虑同步。在 WM_PAINT 中输出变量的 GUI 线程可能不会“看到”该变量已被其他线程更改,但仍会输出旧值。例如,它可能已将变量存储在寄存器中,但仍使用寄存器值,而不是从内存中重新读取实际值。当线程在多个 CPU 内核上运行时,情况会变得更糟,每个线程都有自己的缓存。
它可能一直在您自己的机器上工作,但在用户机器上总是或有时会失败!
同步是一个非常复杂的话题,所以我建议使用您最喜欢的搜索引擎查找“C++ 线程同步”,并准备好阅读一些冗长的内容。 ;-)
您的代码的一个简单解决方案是向线程函数添加一个局部变量i
,并且只在线程内对这个局部变量进行操作(无论如何都是个好主意)。当您发布 WM_APP_MY_THREAD_UPDATE 消息时,您将传递本地 i
作为消息的 WPARAM 或 LPARAM 的参数。
void counterr(HWND hWnd)
int i = 5; // <-- create local variable i instead of accessing global
// to avoid thread synchronization issues
while(i>0)
i -= 1;
// Pass local variable with the message
PostMessage(hWnd, WM_APP_MY_THREAD_UPDATE, static_cast<WPARAM>( i ), 0);
Sleep(1000);
为避免混淆,我会在全局 i
中添加前缀:
int g_i = 0;
然后在 WM_APP_MY_THREAD_UPDATE 的 case
分支中,您将从 WPARAM 参数更新 g_i:
case WM_APP_MY_THREAD_UPDATE:
g_i = static_cast<int>( wParam );
InvalidateRect( hWnd, nullptr, TRUE );
break;
当然你也会在 WM_PAINT 期间使用 g_i:
case WM_PAINT:
// other code here....
std::wstring strOut = std::to_wstring(g_i);
// other code here....
break;
【讨论】:
谢谢,您的回答非常有帮助,程序目前正在运行。以上是关于C ++从异步线程更新Windows窗口的主要内容,如果未能解决你的问题,请参考以下文章
「理解C++20协程原理」从Linux线程线程与异步编程协程与异步