Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[C++] [WinApi] "Zamrażanie" okna aplikacji

Ostatnio zmodyfikowano 2021-06-01 11:34
Autor Wiadomość
idepozapalki
Temat założony przez niniejszego użytkownika
[C++] [WinApi] "Zamrażanie" okna aplikacji
» 2021-05-09 12:36:09
Dzień dobry,

Chciałem zapytać jak poradzić sobie z "zamrażaniem" okna aplikacji podczas uruchamiania przetwarzania długich plików.

Poniżej ćwiczebny szkielet aplikacji.
Tok postępowania:
Uruchamiam aplikacje i rzucam w nią plikiem txt. DropFile wyciąga link i filtruje tylko pliki txt. Następnie funkcja startproces tworzy obiekt process a metoda start uruchamia dość długie przetwarzanie pliku tekstowego.

Mój problem jest taki, że w tym momencie zamrażane jest główne okno aplikacji do czasu skończenia przetwarzanie pliku tekstowego.

Jak widać uruchomiłem wątek. W pierwszym podejściu zakomentowałem linię:
thr1.join();
żeby wątek nie czekał na zakończenie działania obiektu process jednak niczego to nie rozwiązało. Okno aplikacji nie reaguje aż do czasu zakończenia działania processu. Oczywiście dla uproszczenia ten szkielet nie ma niczego w głównym oknie, ale w rzeczywistej aplikacji z pliku tekstowego czytane są informacje które wrzucam do ListView i pojawiają się one dopiero po zakończeniu obróbki pliku testowego, a mogłyby pojawić się od razu po przeczytaniu. Zablokowane są też tooltipy, prawe menu myszki itd aż do końca przetwarzania.

C/C++
//-----------------------------------------------
//
// Program napisany w Microsoft Visual C++ 2019
// Aplikacja napisana w C++ z uzyciem WinApi
//
//-----------------------------------------------

#include <windows.h>
#include <commctrl.h>
#include <string>
#include <thread>

// deklaracje globalnych uchwytow i zmiennych
MSG msg;
HWND hWnd;
HINSTANCE hInst;
LPCWSTR ApplicationTitle = L"Aplikacja";

// klasa przetwarzająca plik tekstowy
class ProcesModul {
public:
   
std::wstring pathToFile;
   
   
ProcesModul( std::wstring _pathToFile ) {
       
this->pathToFile = _pathToFile;
   
}
   
~ProcesModul() { }
   
   
void start() {
       
// tutaj uruchamiane jest przetwarzanie długiego pliku
        // dla uproszczenia w tym miejscu jest tylko okienko informacyjne
       
MessageBox( NULL, L"Koniec przetwarzania", L"Info", MB_ICONINFORMATION | MB_OK );
   
}
}
;

void startproces( std::wstring _plik ) {
   
// tworze pomocniczy obiekt do zbierania danych
   
ProcesModul process( _plik );
   
auto thr1 = std::thread( & ProcesModul::start, & process );
   
thr1.join();
}

//---------------------------------------------------------
//
// Wyciaga rozszerzenie ze sciezki
//
//---------------------------------------------------------
std::wstring GetExtension( const std::wstring & fileName ) {
   
auto pos = fileName.rfind( L"." );
   
if( pos == std::wstring::npos )
       
 pos = - 1;
   
   
return std::wstring( fileName.begin() + pos + 1, fileName.end() );
}

//---------------------------------------------------------
//
// Obsluga rzuconych plikow
//
//---------------------------------------------------------
void DropFile( WPARAM wParam ) {
   
TCHAR lpszFile[ MAX_PATH ] = { 0 };
   
UINT uFile = 0;
   
HDROP hDrop =( HDROP ) wParam;
   
int index, count, length;
   
count = DragQueryFile( hDrop, 0xFFFFFFFF, NULL, 0 ); // ilosc rzuconych plikow
    // petla bedzie czytac rzucone pliki
   
for( index = 0; index < count; ++index ) {
       
length = DragQueryFile( hDrop, index, NULL, 0 );
       
if( length > 0 ) {
           
TCHAR * fname = new TCHAR[ static_cast < size_t >( length ) + 1 ]; // rzutuje
           
DragQueryFile( hDrop, index, fname, length + 1 );
           
// konwertuje tchar do wstringa
           
std::wstring sciezka( & fname[ 0 ] );
           
std::wstring rozszerzenie;
           
rozszerzenie = GetExtension( sciezka );
           
// filtruje pliki i do przetwarzania puszczam tylko aptsource
           
if( rozszerzenie == L"txt" ) {
               
startproces( sciezka );
           
}
           
delete[ ] fname;
       
}
    }
   
DragFinish( hDrop );
}

//---------------------------------------------------------
//
// Procedura obsługująca wiadomości dla okna, wywoływana przez
// MS-Windows przy okazji różnych zdarzeń, które należy obsłużyć
//
//--------------------------------------------------------------
static LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
   
switch( msg )
   
{
       
// Obsluga drag & drop
   
case WM_DROPFILES: {
           
DropFile( wParam );
           
break;
       
}
       
       
// reakcja na zamkniecie okienka
   
case WM_CLOSE:
       
PostQuitMessage( 0 );
       
return 0;
       
       
// komunikat od systemu wyslany po zamknieciu okienka
   
case WM_DESTROY:
       
PostQuitMessage( 0 );
       
return 0;
       
       
default:
       
return DefWindowProc( hWnd, msg, wParam, lParam );
       
break;
   
}
   
return DefWindowProc( hWnd, msg, wParam, lParam );
}

void InitMainWnd() {
   
HINSTANCE hInstance = GetModuleHandle( 0 );
   
WNDCLASSEX wClass = { 0 };
   
ZeroMemory( & wClass, sizeof( WNDCLASSEX ) );
   
wClass.style = CS_HREDRAW | CS_VREDRAW;
   
wClass.cbClsExtra = 0;
   
wClass.cbWndExtra = 0;
   
wClass.cbSize = sizeof( WNDCLASSEX );
   
wClass.hbrBackground =( HBRUSH )( COLOR_BTNFACE + 1 );
   
wClass.hInstance = hInstance;
   
wClass.lpfnWndProc =( WNDPROC ) WndProc;
   
wClass.lpszClassName = L"My Window Class";
   
wClass.hCursor = LoadCursor( 0, IDC_ARROW );
   
wClass.hIcon = LoadIcon( NULL, IDI_APPLICATION );
   
wClass.hIconSm = LoadCursor( NULL, IDC_ARROW );
   
wClass.lpszMenuName = 0;
   
// Rejestrujemy klasę okna w Windows.
   
if( !RegisterClassEx( & wClass ) ) {
       
int nResult = GetLastError();
       
MessageBox( NULL, L"Nie utworzono żadnej klasy!", L"Błąd", MB_ICONERROR );
   
}
}

//---------------------------------------------------------
//
// Funkcja tworząca główne okno
//
//---------------------------------------------------------
HWND CreateMainWnd() {
   
return
   
CreateWindowEx(
   
WS_EX_CLIENTEDGE,
   
L"My Window Class",
   
ApplicationTitle,
   
WS_OVERLAPPED | WS_CAPTION | WS_BORDER | WS_MINIMIZEBOX | WS_SYSMENU,
   
0,
   
0,
   
400,
   
400,
   
NULL,
   
NULL,
   
hInst,
   
NULL );
}

//---------------------------------------------------------
//
// Główna funkcja w Windows , od której uruchamiany jest program
// kodowanie zależne od ustawień projektu
//
//---------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int showCmd )
{
   
InitMainWnd(); // startuje głowne okno
   
hWnd = CreateMainWnd(); // create the main window!
   
if( !hWnd ) {
       
int nResult = GetLastError();
       
MessageBox( NULL, L"Nie utworzono żadnego okna!", L"Błąd", MB_ICONERROR );
   
}
   
DragAcceptFiles( hWnd, true ); // Drag & Drop w starym stylu
   
ShowWindow( hWnd, showCmd ); // Utworzone okno wyświetlamy na ekranie  
   
UpdateWindow( hWnd ); // Uaktualniamy treść okna
   
ZeroMemory( & msg, sizeof( MSG ) );
   
while( msg.message != WM_QUIT ) { // Rozpoczynamy pętlę obsługi wiadomości napływających do naszego okna.
       
if( PeekMessage( & msg, 0, 0, 0, PM_REMOVE ) ) {
           
TranslateMessage( & msg ); // Przekształcamy wiadomość
           
DispatchMessage( & msg ); // Wysyłamy ją do procedury okna
       
}
    }
   
return 0;
}
P-178568
pekfos
» 2021-05-09 12:42:50
Utwórz jakąś pulę, lub jeden wątek do przetwarzania plików i skomunikuj te wątki z głównym przez kolejkę zadań do wykonania.
P-178569
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-09 14:04:45
W swojej naiwności sądziłem, że w tym momencie tworze kolejny wątek:

C/C++
auto thr1 = std::thread( & ProcesModul::start, & process );
thr1.join();

i główne okno aplikacji będzie od niego niezależne.

Powinienem więc na spokojnie poczytać tą lekcję:

https://cpp0x.pl/kursy/Kurs-WinAPI-C++/Zaawansowane/Watki/337

i tu znajdę rozwiązanie mojego dylematu ?
P-178570
pekfos
» 2021-05-09 15:25:19
Co z tego wątku, skoro czekasz na jego zakończenie? Powinieneś mieć stałą ilość wątków do przetwarzania plików i kolejkę zadań. Jak będziesz tworzyć wątki na żądanie, to do programu będzie można wrzucać pliki aż do wyczerpania zasobów.

Powinienem więc na spokojnie poczytać tą lekcję:

https://cpp0x.pl/kursy/Kurs-WinAPI-C++/Zaawansowane/Watki/337

i tu znajdę rozwiązanie mojego dylematu ?
Jeśli nie masz żadnego pojęcia o wielowątkowości, to i tak powinieneś się z tym zapoznać. Niezależnie czy jest tam rozwiązanie, czy nie.
P-178571
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-18 18:14:10
Przerobiłem lekcję 26 o wątkach.
Z notatki o GUI wynika, że główne okno programu będzie tak czy inaczej nieaktywne dopóki nie zakończy się przetwarzać uruchomiony wątek. Dodatkowo jeżeli ten wątek będzie wymagał jakiejś interakcji z użytkownikiem to też nic z tego. Przykładowo w wyniku działania wątku chcę zapisać jakiś plik na dysku, rozmiaru pliku przed uruchomieniem wątku nie znam więc w czasie trwania tego wątku trzeba sprawdzić dostępne miejsce na dysku, a jeżeli jest go za mało to pokazać okienko z wyborem miejsca na zapis. Nie mogę również pokazać na GUI choćby procentowego etapu na którym jest wątek, na progresbarze też tego nie pokażę.

Czy dobrze myślę?
Główny wątek odpowiada za odświeżanie okna aplikacji i dopóki nie doczeka się na WaitForSingleObject lub WaitForMultipleObjects to na powierzchni GUI nic się nie ukaże (choćby ten progressbar).
Różnicą pomiędzy moim pierwszym podejściem z głównym wątkiem (gdy uruchamiając go mam kręcącą się klepsydrę na oknie aplikacji i jej zamrożenie), a użyciem dodatkowego wątku będzie taka, że będę mógł poruszać oknem tej aplikacji, ale dopóki nie zakończy się przetwarzanie to na ListView nie zobaczę niczego, progressbar też dopiero na końcu pokaże mi 100% ?
P-178627
pekfos
» 2021-05-19 17:36:09
Czy dobrze myślę?
Nie. Okno jest niezależne od utworzonych wątków, dopóki nie robisz twardej synchronizacji jak np czekanie na koniec wątku. Żeby mieć interaktywne okno musisz tylko skomunikować wątek główny z wątkiem wykonującym pracę. Żeby zlecić pracę, najlepiej użyj jakiejś kolejki. Do sygnalizacji w drugą stronę możesz użyć SendMessage().
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage
If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code.
P-178628
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-19 18:00:40
A co z interakcją z użytkownikiem o której pisałem wyżej?
SendMessage() może wysyłać informacje do progressbaru, ale o wyborze okna do zapisu mogę zapomnieć? SendMessage() może wysłać informację typu "Chłopie, przetworzyło się, ale na tym dysku nie ma miejsca więc sorry!"
Co z odświeżaniem tego wszystkiego?

Zastanawiałem się co mogę praktycznie zrobić i mógłbym zrobić na pierwszym wątku zegar uruchamiający się co 50ms i zatrzymywać drugi wątek liczący na małą chwilę i zrobić przykładowo RedrawWindow. Czy w tym momencie dotrą informacje do GUI o tym przykładowym zapisie ?
Czy to może zadziałać ?
P-178629
pekfos
» 2021-05-19 18:57:57
Tak czytam tą sekcję "Wątki i GUI" i przekaz jest raczej nieoczywisty, a już na pewno zapominanie o GUI to przesada.
Jeśli na przykład spróbujemy z wątku zrobić cokolwiek z GUI, a główny wątek czeka na zakończenie tej operacji w funkcji WaitForSingleObject, to nasza aplikacja się zawiesi.
Jeśli w procedurze obsługi okna czekasz na zakończenie wątku, to już robisz źle. W podanym tam kodzie występuje zakleszczenie przez to że procedura okna czeka na zakończenie wątku, w którym jest utworzony dialog modalny z tym oknem jako ownerem. Jeśli podasz tam NULL, MessageBox zadziała i okno główne nie będzie reagować, a jak nie będziesz czekać na koniec wątku, to będziesz mieć oba okna funkcjonujące niezależnie.

mógłbym zrobić na pierwszym wątku zegar uruchamiający się co 50ms i zatrzymywać drugi wątek liczący na małą chwilę i zrobić przykładowo RedrawWindow.
Niby jak chcesz zatrzymać wątek i.. po co..? Po to masz osobny wątek, żeby wykonywać kod równolegle do obsługi okna.
P-178630
« 1 » 2 3 4
  Strona 1 z 4 Następna strona