所以我刚刚开始使用C ++,并希望创build一个窗口,其中包含一个button,用于启动一个计数器的asynchronous线程,该计数器从5到0计数,这表示耗时很长的任务。 该数字应该已经显示在窗口上,并在计数器计数时每秒更新一次。 为此,子线程必须以任何方式与主窗口线程的消息循环进行通信。 我试图做到这一点:
但是在这两种情况下,窗口都没有得到更新。 所以我怀疑错误发送窗口句柄从主线程的子线程或发送UpdateWindow消息从子线程到主线程或两者,或者我完全偏离轨道,everythig是错误的。
也许我的思维方式也是错误的,我应该用另一种方式来做,但是我不知道我该怎么开始。
#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; }
标准的东西再次和Code的结束
通常的做法是使用自定义消息ID调用SendMessage()或PostMessage()来通知UI有关线程所做的一些更改。
直接从线程更新UI是不好的做法,因为线程只应该做“工作”,而不用担心这个工作的结果将如何呈现。
您已经通过使用PostMessage在正确的轨道上。 但是不要使用WM_PRINT,你应该像这样定义一个自定义消息ID:
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_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;