2017-02-18 7 views
2

Итак, я только начал с C++ и хотел создать окно с кнопкой, которая запускает асинхронный поток для счетчика, который насчитывает от 5 до 0, что представляет собой долговременную задачу. Номер должен был отображаться в окне и обновляться каждую секунду, пока счетчик подсчитывает. Для этого дочерний поток должен каким-либо образом взаимодействовать с контуром сообщения в потоке основного окна. Я пытался сделать это:C++ update windows window из async thread

  • Отправка UpdateWindow с WindowHandle главного окна
  • Отправка PostMessage с WindowHandle главного окна

Но в обоих случаях окно не получает updatet. Поэтому я подозреваю ошибку, либо отправив дескриптор окна из основного потока в дочерний поток, либо отправив сообщение 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; 
} 

стандартные вещи снова и конец Кодекса

+0

Вы не должны обновлять поток пользовательского интерфейса из других потоков. У него будет неопределенное поведение – Asesh

+0

, так как же мне это делать? – spaghetticode

+0

Эта тема ответит на ваш вопрос: http://stackoverflow.com/questions/3783713/c-win32-executing-a-method-on-ui-thread-due-to-an-event-on-background-thread – Asesh

ответ

2

Обычный способдля этого необходимо либо вызвать SendMessage(), либо PostMessage() с помощью специального сообщения ID, чтобы уведомить пользовательский интерфейс о некоторых изменениях, сделанных потоком.

Обновление пользовательского интерфейса непосредственно из потока - это плохая практика, потому что поток должен работать только «работать» и не беспокоиться о том, как результаты этой работы будут представлены в пользовательском интерфейсе.

Вы уже были на правильном пути, используя PostMessage. Но вместо того, чтобы использовать WM_PRINT вы должны определить пользовательский идентификатор сообщения, как это:

const UINT WM_APP_MY_THREAD_UPDATE = WM_APP + 0;

Сообщение в диапазоне WM_APP через 0xBFFF зарезервированы для частного использования приложения, так что вам не придется беспокоиться, что некоторые Компонент Windows уже использует ваш идентификатор сообщения.

Ваша функция потока затем вызывает:

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 без принятия синхронизации.Поток GUI, который выводит переменную в WM_PAINT, может не «видеть», что переменная была изменена другим потоком и все еще выводит старое значение. Например, он может сохранить переменную в регистре и все еще использует значение регистра вместо перечитывания фактического значения из памяти. Вопросы становятся хуже, когда потоки выполняются на нескольких ядрах процессора, где каждый поток имеет свой собственный кеш. Он может работать все время на вашей собственной машине, но всегда или иногда терпит неудачу на машинах пользователей!

Синхронизация - очень сложная тема, поэтому я предлагаю искать «синхронизацию потоков на 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; 

Тогда в case ветви для WM_APP_MY_THREAD_UPDATE вы обновит g_i от параметра WPARAM:

case WM_APP_MY_THREAD_UPDATE: 
    g_i = static_cast<int>(wParam); 
    InvalidateRect(hWnd, nullptr, TRUE); 
    break; 

Конечно, вы также должны использовать g_i во время WM_PAINT:

case WM_PAINT: 
    // other code here.... 
    std::wstring strOut = std::to_wstring(g_i); 
    // other code here.... 
    break; 
+0

Спасибо, ваш ответ был очень полезным, и программа работает пока. – spaghetticode