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. #include <windows.h> #include <commctrl.h> #include <string> #include <thread>
MSG msg; HWND hWnd; HINSTANCE hInst; LPCWSTR ApplicationTitle = L"Aplikacja";
class ProcesModul { public: std::wstring pathToFile; ProcesModul( std::wstring _pathToFile ) { this->pathToFile = _pathToFile; } ~ProcesModul() { } void start() { MessageBox( NULL, L"Koniec przetwarzania", L"Info", MB_ICONINFORMATION | MB_OK ); } };
void startproces( std::wstring _plik ) { ProcesModul process( _plik ); auto thr1 = std::thread( & ProcesModul::start, & process ); thr1.join(); }
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() ); }
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 ); 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 ]; DragQueryFile( hDrop, index, fname, length + 1 ); std::wstring sciezka( & fname[ 0 ] ); std::wstring rozszerzenie; rozszerzenie = GetExtension( sciezka ); if( rozszerzenie == L"txt" ) { startproces( sciezka ); } delete[ ] fname; } } DragFinish( hDrop ); }
static LRESULT CALLBACK WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_DROPFILES: { DropFile( wParam ); break; } case WM_CLOSE: PostQuitMessage( 0 ); return 0; 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; if( !RegisterClassEx( & wClass ) ) { int nResult = GetLastError(); MessageBox( NULL, L"Nie utworzono żadnej klasy!", L"Błąd", MB_ICONERROR ); } }
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 ); }
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int showCmd ) { InitMainWnd(); hWnd = CreateMainWnd(); if( !hWnd ) { int nResult = GetLastError(); MessageBox( NULL, L"Nie utworzono żadnego okna!", L"Błąd", MB_ICONERROR ); } DragAcceptFiles( hWnd, true ); ShowWindow( hWnd, showCmd ); UpdateWindow( hWnd ); ZeroMemory( & msg, sizeof( MSG ) ); while( msg.message != WM_QUIT ) { if( PeekMessage( & msg, 0, 0, 0, PM_REMOVE ) ) { TranslateMessage( & msg ); DispatchMessage( & msg ); } } return 0; }
|
|
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. |
|
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: 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 ? |
|
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. |
|
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% ?
|
|
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-sendmessageIf 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. |
|
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ć ? |
|
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. |
|
« 1 » 2 3 4 |