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
» 2021-05-20 18:32:23
Rzeczywiście ta lekcja z wątkami jest trochę niejasna, ale posiedziałem trochę z VS nad przykładami i je uruchomiłem.

Tutaj jest to czego używałem do testów lekko odbiegające od pierwowzoru pozwalające w konsoli zobaczyć co się dzieje i testować sobie samodzielnie kolejne etapy.

C/C++
#include <process.h>  // wymagane ze względu na _beginthread
#include <Windows.h>
#include <iostream>
#include <list>

CRITICAL_SECTION g_Section;

struct STask {
   
int taskType;
   
int userID;
   
unsigned int taskTime;
   
std::string taskMsg;
   
void Process( void ) {
       
std::cout << "Struktura nr " << taskType << std::endl;
       
Sleep( 500 ); // z braku lepszego zajęcia... ;-)
   
}
}
;
std::list < STask > g_TaskQueue;

// __cdecl czyści na początku stos wątku
void __cdecl ThreadProc( void * Args ) {
   
while( !g_TaskQueue.empty() ) {
       
EnterCriticalSection( & g_Section ); // początek sekcji krytycznej
       
STask task = g_TaskQueue.back();
       
std::cout << "Watek nr" << task.taskType << std::endl;
       
g_TaskQueue.pop_back();
       
LeaveCriticalSection( & g_Section ); // ...i jej koniec        
       
task.Process();
   
} // czekaj aż wątek przerobi wszystkie elementy z listy
   
_endthread();
}

int main() {
   
InitializeCriticalSection( & g_Section );
   
// utwórz strukture z zainicjowanymi danymi poczatkowymi
   
STask task { 0, 0, 0, "Some text" };
   
// umieść na  liście 10 takich struktur
   
for( int i = 0; i < 10; ++i ) {
       
task.taskType = i; // numeruj elementy
       
g_TaskQueue.push_back( task );
   
}
   
// utwórz nowy watek
   
HANDLE hThread =( HANDLE ) _beginthread( ThreadProc, 0, NULL );
   
WaitForSingleObject( hThread, INFINITE );
   
std::cout << "Watek zakonczyl dzialanie." << std::endl;
   
DeleteCriticalSection;
}

/*
// drugi przykład (wyścig wątków)
#include <process.h> // wymagane ze względu na _beginthread
#include <Windows.h>
#include <iostream>
#include <list>

struct STask {
    int taskType;
    int userID;
    unsigned int taskTime;
    std::string taskMsg;
    void Process(void) {
        std::cout << "Struktura nr " <<taskType << std::endl;
        Sleep(500); // z braku lepszego zajęcia... ;-)
    }
};

std::list < STask > g_TaskQueue;

void __cdecl ThreadProc(void* Args) {
    while (!g_TaskQueue.empty()) {
        STask task = g_TaskQueue.back();
        std::cout << "Watek nr" << task.taskType << std::endl;
        g_TaskQueue.pop_back();
        task.Process();
    }
    _endthread();
}

int main() {
    // utwórz strukture z zainicjowanymi danymi poczatkowymi
    STask task{0, 0, 0, "Some text"};
    // umieść na  liście 10 takich struktur
    for (int i = 0; i < 10; ++i) {
        task.taskType = i;// numeruj elementy
        g_TaskQueue.push_back(task);
    }
    // utwórz nowy watek
    HANDLE hThread = (HANDLE)_beginthread(ThreadProc, 0, NULL);
    // jeżeli lista nie jest pusta to:
    while (!g_TaskQueue.empty()) {
        // wrzuć ostatni element z listy do struktury
        STask task = g_TaskQueue.back();
        std::cout << "Element nr " << task.taskType << std::endl;
        // skasuj ostatni element z listy
        g_TaskQueue.pop_back();
        // uruchom funkcje Process() z wrzuconego elementu listy
        task.Process();
    } // pętla się kręci aż w liście zabraknie elementów
    // czekaj aż wątek przerobi wszystkie elementy z listy
    WaitForSingleObject(hThread, INFINITE);
    std::cout << "Watek zakonczyl dzialanie." << std::endl;
}
*/

/*
// pierwszy przykład
#include <process.h> // wymagane ze względu na _beginthread
#include <Windows.h>
#include <iostream>

unsigned int g_Counter = 0;

void __cdecl ThreadProc(void* Args) {
    while (g_Counter < 1000) {
        g_Counter++;
    }
    _endthread();
}

int main() {        
        HANDLE hEvent = CreateEvent(
            NULL,   // wskaźnik do struktury opisującej atrybuty bezpieczeństwa
            TRUE,   // czy zdarzenie powinno automatycznie przechodzić w stan niezasygnalizowany po tym, jak czekający wątek zostanie odblokowany, TRUE to ręcznie przestawienie stanu
            FALSE,  // początkowy stan obiektu, FALSE to stan niezasygnalizowany
            NULL);  // nazwa zdarzenia

        HANDLE hThread = (HANDLE)_beginthread(ThreadProc, 0, NULL);
        WaitForSingleObject(hThread, INFINITE);
        char buffer[1024];
        sprintf_s(buffer, "g_Counter: %d\n", g_Counter);
        std::cout << buffer << std::endl;    
}
*/

Może komuś się przyda do szybszego przerobienia tej lekcji i zobaczenia niuansów na konsoli.
Na GUI nie testowałem, ale jeżeli to konieczne to sprawdzę krok po kroku.
Przyjąłem opisane tam informacje na słowo i wyciągnąłem własne wnioski - jak widać niepoprawne.

Może jeszcze raz wrzucę mój "kod roboczy" nad którym utknąłem:

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>
#include <process.h>  
// wymagane ze względu na _beginthread
#include <vector>

#pragma comment(lib,"comctl32.lib")
// użycie stylu z XP do grafiki do ListView
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version = '6.0.0.0' processorArchitecture = '*' publicKeyToken = '6595b64144ccf1df' language = '*'\"")

// deklaracje globalnych uchwytow i zmiennych
MSG msg;
HWND hWnd;
HINSTANCE hInst;
static HWND hwndListView;
#define ID_LISTVIEW                     1000
LVITEM col;
LPCWSTR ApplicationTitle = L"Aplikacja";
std::wstring fileName = L"C:\1\Testy.txt"; // testowy plik

struct Task {
   
int ElementNr = 0; // numer elementu
   
std::wstring path; // pełna sciezka w formacie wstring
   
std::wstring Produkt; // nazwa produktu
   
int iProgress = 0; // postep
};
static std::vector < Task > StructureInVector; // w wektorze beda przechowywane struktury z zadaniami


// 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();
}

//---------------------------------------------------------
//
// Przykład z MSDN z plikiem wpisanym na stałe
//  rozmiar przykładowego pliku 94MB
//
//---------------------------------------------------------
BOOL ParseALargeFile( HWND hWnd )
{
   
RECT rcClient; // Client area of parent window.
   
int cyVScroll; // Height of scroll bar arrow.
   
HWND hwndPB; // Handle of progress bar.
   
HANDLE hFile; // Handle of file.
   
DWORD cb; // Size of file and count of bytes read.
   
DWORD filesize;
   
LPCH pch; // Address of data read from file.
   
LPCH pchTmp; // Temporary pointer.
    // Ensure that the common control DLL is loaded, and create a progress bar
    // along the bottom of the client area of the parent window.
    // Base the height of the progress bar on the height of a scroll bar arrow.
   
GetClientRect( hWnd, & rcClient );
   
// dodaje pasek progressbaru na oknie
   
hwndPB = CreateWindowEx( 0, PROGRESS_CLASS,( LPTSTR ) NULL,
   
WS_CHILD | WS_VISIBLE, rcClient.left,
   
300, // położenie progressbaru w Y
   
rcClient.right,
   
30, // szerokość paska progressbaru
   
hWnd,( HMENU ) 0, hInst, NULL );
   
// Open the file for reading, and retrieve the size of the file.
    // ścieżka do pliku ustawiona na sztywno
   
hFile = CreateFile( L"C:\\1\\Testy2.txt", GENERIC_READ, FILE_SHARE_READ,
   
( LPSECURITY_ATTRIBUTES ) NULL, OPEN_EXISTING,
   
FILE_ATTRIBUTE_NORMAL,( HANDLE ) NULL );
   
if( hFile ==( HANDLE ) INVALID_HANDLE_VALUE )
       
 return FALSE;
   
   
cb = GetFileSize( hFile,( LPDWORD ) NULL ); // rozmiar pliku w bajtach
   
filesize = cb;
   
// Set the range and increment of the progress bar.
   
SendMessage( hwndPB, PBM_SETRANGE, 0, MAKELPARAM( 0, cb / 2048 ) ); // Sets the minimum and maximum values for a progress bar and redraws the bar to reflect the new range
   
SendMessage( hwndPB, PBM_SETSTEP,( WPARAM ) 1, 0 ); // Specifies the step increment for a progress bar
    // Parse the file.
   
pch =( LPCH ) LocalAlloc( LPTR, sizeof( char ) * 2048 );
   
pchTmp = pch;
   
int tsize;
   
int bsize;
   
do {
       
ReadFile( hFile, pchTmp, sizeof( char ) * 2048, & cb,( LPOVERLAPPED ) NULL );
       
       
// szacowanie procentowego postępu i umieszczenie go w strukturze
        // na razie nie testowane, do sprawdzenia
       
tsize = strlen( pchTmp );
       
bsize =( 100 + tsize ) / filesize;
       
StructureInVector.at( 0 ).iProgress = bsize;
       
       
SendMessage( hwndPB, PBM_STEPIT, 0, 0 );
   
} while( cb );
   
   
CloseHandle(( HANDLE ) hFile );
   
DestroyWindow( hwndPB );
   
return TRUE;
}

//---------------------------------------------------------
//
// 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() );
}

void AddNewItemOnListView( int i ) {
   
TCHAR pszBuffer[ 16 ]; // bufor na cyferke na progresbarze
    // sprawdzam czy w wektorze ze strukturami cos jest
   
if( !StructureInVector.empty() ) { // sprawdzam czy w wektorze ze strukturami cos jest
        // pierwsza kolumna (oznaczona jako 0)
       
col.mask = LVIF_PARAM | LVIF_TEXT;
       
col.iItem = i;
       
col.iSubItem = 0;
       
// tu robię wskaźnik na fileName
       
col.pszText = LPTSTR(( LPCTSTR )( StructureInVector.at( i ).path ).c_str() );
       
col.lParam = StructureInVector.at( i ).iProgress; // tutaj ustawia sie dlugosc progresbaru wizualnie
       
SendMessageW( hwndListView, LVM_INSERTITEM, 0,( LPARAM ) & col );
       
       
// druga kolumna (oznaczona jako 1)
       
col.mask = LVIF_TEXT;
       
col.iSubItem = 1;
       
col.pszText = LPTSTR(( LPCTSTR )( StructureInVector.at( i ).Produkt ).c_str() );
       
SendMessageW( hwndListView, LVM_SETITEM, 0,( LPARAM ) & col );
       
       
// pierwsza kolumna (oznaczona jako 2)
        // pasek progresbaru, ale tutaj wpisuje sie tylko jego procentowa wartosc jako cyfre
       
col.iSubItem = 2;
       
col.pszText = pszBuffer;
       
swprintf_s( pszBuffer, L"%d %%", StructureInVector.at( i ).iProgress );
       
SendMessageW( hwndListView, LVM_SETITEM, 0,( LPARAM ) & col );
   
}
}

static LRESULT HandleCustomDraw( NMLVCUSTOMDRAW * pcd ) {
   
TCHAR buffer[ 40 ];
   
switch( pcd->nmcd.dwDrawStage ) {
   
case CDDS_PREPAINT:
       
/* Tell the control we are interested in per-item notifications.
   * (We need it just to tell the control we want per-subitem
   * notifications.) */
       
return CDRF_DODEFAULT | CDRF_NOTIFYITEMDRAW;
       
       
case( CDDS_ITEM | CDDS_PREPAINT )
            :
/* Tell the control we are interested in per-subitem notifications. */ return CDRF_DODEFAULT | CDRF_NOTIFYSUBITEMDRAW;
       
       
case( CDDS_ITEM | CDDS_SUBITEM | CDDS_PREPAINT )
            :
switch( pcd->iSubItem )
       
{
       
case 1:
           
// obsluga drugiej kolumny danych
            /* Customize "priority" column by marking some priority levels
    * with appropriate color. */
           
col.iSubItem = pcd->iSubItem;
           
col.pszText = buffer;
           
col.cchTextMax = sizeof( buffer ) / sizeof( buffer[ 0 ] );
           
SendMessage( hwndListView, LVM_GETITEMTEXT, pcd->nmcd.dwItemSpec,( LPARAM ) & col ); // LVM_GETITEMTEXT Retrieves the text of a list-view item or subitem.
            /* Let the control do the painting itself with the new color. */
           
return CDRF_DODEFAULT;
           
       
case 2:
           
// Customize "progress" column. We paint simple progress indicator.
           
int iProgress = pcd->nmcd.lItemlParam;
           
int cx;
           
HDC hdc = pcd->nmcd.hdc;
           
COLORREF clrBack;
           
HBRUSH hBackBrush;
           
HBRUSH hProgressBrush;
           
HBRUSH hOldBrush;
           
HPEN hPen;
           
HPEN hOldPen;
           
RECT rc;
           
           
clrBack = pcd->clrTextBk;
           
if( clrBack == CLR_NONE || clrBack == CLR_DEFAULT )
               
 clrBack = RGB( 255, 255, 255 ); // tło pod progresbarami
           
           
hBackBrush = CreateSolidBrush( clrBack );
           
hProgressBrush = CreateSolidBrush( RGB( 190, 190, 255 ) ); // kolory pasków
           
hPen = CreatePen( PS_SOLID, 0, RGB( 190, 190, 255 ) ); // obwódka progresbaru
           
           
hOldBrush =( HBRUSH ) SelectObject( hdc, hBackBrush );
           
FillRect( hdc, & pcd->nmcd.rc, hBackBrush );
           
           
cx = pcd->nmcd.rc.right - pcd->nmcd.rc.left - 6;
           
if( cx < 0 )
               
 cx = 0;
           
           
rc.left = pcd->nmcd.rc.left + 3; // ustawinie lewego brzegu progressbaru
           
rc.top = pcd->nmcd.rc.top + 2;
           
rc.right = rc.left + cx * iProgress / 100;
           
rc.bottom = pcd->nmcd.rc.bottom - 2;
           
SelectObject( hdc, hProgressBrush );
           
FillRect( hdc, & rc, hProgressBrush ); // wypełnienie progressbaru kolorem
           
           
rc.right = pcd->nmcd.rc.right - 3;
           
SelectObject( hdc, GetStockObject( HOLLOW_BRUSH ) );
           
hOldPen =( HPEN ) SelectObject( hdc, hPen );
           
Rectangle( hdc, rc.left, rc.top, rc.right, rc.bottom ); // rysuje obwódkę na około progressbaru
           
           
col.iSubItem = pcd->iSubItem;
           
col.pszText = buffer;
           
col.cchTextMax = sizeof( buffer ) / sizeof( buffer[ 0 ] );
           
SendMessage( hwndListView, LVM_GETITEMTEXT, pcd->nmcd.dwItemSpec,( LPARAM ) & col ); // wrzucenie tekstu
           
DrawText( hdc, buffer, - 1, & rc, DT_CENTER | DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS );
           
           
SelectObject( hdc, hOldBrush );
           
DeleteObject( hProgressBrush );
           
DeleteObject( hBackBrush );
           
SelectObject( hdc, hOldPen );
           
DeleteObject( hPen );
           
           
// Tell the control to not paint as we did so.
           
return CDRF_SKIPDEFAULT; // brak tego nie narysuje nic
       
}
       
break;
   
}
   
// For all unhandled cases, we let the control do the default.
   
return CDRF_DODEFAULT;
}

//---------------------------------------------------------
//
// Funkcja robi opisy na belce nad ListView
//
//---------------------------------------------------------
static void CreateListViewLabels() {
   
LVCOLUMN col;
   
// tutaj są etykietki na belce nad wrzuconymi stringami
   
col.mask = LVCF_WIDTH | LVCF_TEXT;
   
// piersza pozycja na pasku
   
col.pszText =( LPWSTR ) L"Przetwarzany plik";
   
col.cx = 265;
   
SendMessageW( hwndListView, LVM_INSERTCOLUMN, 0,( LPARAM ) & col );
   
// druga pozycja na pasku
   
col.pszText =( LPWSTR ) L"Produkt";
   
col.cx = 60;
   
SendMessageW( hwndListView, LVM_INSERTCOLUMN, 1,( LPARAM ) & col );
   
// trzecia pozycja na pasku
   
col.pszText =( LPWSTR ) L"Postęp";
   
col.cx = 80;
   
SendMessageW( hwndListView, LVM_INSERTCOLUMN, 2,( LPARAM ) & col );
}

void AddControls( HWND hWnd ) {
   
hwndListView = CreateWindowEx( 0, WC_LISTVIEW, NULL, LVS_REPORT | WS_TABSTOP | WS_BORDER | WS_CHILD | WS_VISIBLE,
   
10, 10, 600, 250, hWnd,( HMENU ) ID_LISTVIEW, hInst, 0 );
}

//---------------------------------------------------------
//
// 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 )
   
{
   
case WM_CREATE:
       
AddControls( hWnd );
       
CreateListViewLabels();
       
break;
       
   
case WM_NOTIFY:
       
{
           
NMHDR * pHdr =( NMHDR * ) lParam;
           
if( pHdr->idFrom == ID_LISTVIEW && pHdr->code == NM_CUSTOMDRAW )
               
 return HandleCustomDraw(( NMLVCUSTOMDRAW * ) pHdr );
           
           
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,
   
650,
   
400,
   
NULL,
   
NULL,
   
hInst,
   
NULL );
}

//---------------------------------------------------------
//
// Do obsługi LisView
//
//---------------------------------------------------------
void CreateCommonControls() {
   
// inicjacja klasy pasków chyba sa niepotrzebne narazie
   
INITCOMMONCONTROLSEX cc;
   
cc.dwSize = sizeof( INITCOMMONCONTROLSEX );
   
cc.dwICC = ICC_PROGRESS_CLASS;
   
cc.dwICC = ICC_BAR_CLASSES; // toolbary, statusbary, tooltipy i oczywiście progressbary
   
InitCommonControlsEx( & cc );
}

//---------------------------------------------------------
//
// 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 );
   
}
   
// wstawiam pierwszy element do listView
   
CreateCommonControls();
   
Task t { 0, fileName, L"Produkt2", 8 }; // testowe ustawienie 8% na progressbarze i opisy
   
StructureInVector.push_back( t ); // dodaje strukture do vectora
   
AddNewItemOnListView( 0 );
   
ShowWindow( hWnd, showCmd ); // Utworzone okno wyświetlamy na ekranie  
   
UpdateWindow( hWnd ); // Uaktualniamy treść okna
   
ParseALargeFile( hWnd );
   
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;
}

Jak widać kolejne zadania trzymam w wektorze ze strukturami. Są dodawane przez drag&drop lub z innych instacji, ale usunąłem te fragmenty żeby nie koncentrować na nich niepotrzebnie uwagi.
W przykładzie używam trochę zmodyfikowany ListView, bo taka konstrukcja odpowiadałaby mi najbardziej w pokazywaniu detali na bieżąco.
Tutaj jest pozycja z jednym "Produktem2" ale docelowo było by ich więcej.
Początko sądziłem, że moje problemy z pokazywaniem informacji to wina odświeżania, finalnie okazało się jednak że to główny wątek pod dostatniu większego pliku "zamraża" GUI, ale w tle robi to co powienien.
Problem widać już na dość niewielkich plikach. Exe'k w wersji x64Debug ma problem już z plikami o wielkości 1MB.
Wiem, że robocza wersja to Relase, ale ona też tak będzie zachowywać się przy plikach rzędu 100MB i większych.
Dla uproszczenia nie uruchamiam nigdzie startproces.
Pasek progressbaru z funkcji ParseALargeFile() tworzę, żeby zobaczyć, że plik jest wczytywany.
Coś z nim jest nie tak, bo dochodzi do 10% i znika. Wystarczy jednak zakomentować linię:

#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version = '6.0.0.0' processorArchitecture = '*' publicKeyToken = '6595b64144ccf1df' language = '*'\"")

i to rozwiązuje znikanie, a psuje ListView - ale to nie stanowi problemu.
Wątek główy wykonuje swoje zadanie poprawnie. Sprawdza czy da się otowrzyć plik, jeżeli nie to są informacje.
Zapisuje też wynik ze sprawdzeniem dostępności miejsca na dysku. Jeżeli jest problem to jest okienko z wyborem wolnej większej przestrzeni. Jest interakcja z użytkownikiem. Na końcu wyświetlane są informacje o przetwarzaniu. Tej funkcji też nie wrzuciłem, bo jest niekoniczna.

Moje zasadnicze pytanie jest takie czy przy użyciu dodatkowego wątku mogę (tworząć obiekt z klasy ProcesModul) mieć te same funkcjonalności o których napisałem wyżej. Oczywiście tam w tym momencie jest join które niczego nie rozwiązuje.

Mój pomysł z poprzedniego postu z timerem na pierwszym wątku wynika z obserwacji wielowątkowych programów i tam znalazłem takie rozwiązanie.
Spekulacje nad zatrzymywaniem wątku z obiektem z ProcesModul są tylko moje, bo staram się rozwiązać mój dylemat w jakiś prosty sposób.
Jeżeli funkcjonalności które chce osiągnąć przy użyciu wątków da się zrobić, to wystarczy mnie pchnąć w tym kierunku, będę starał się doedukować w tej tematyce i dopytywać.
Jeżeli nie to może jest jakaś inna droga ?

Czytałem o std::async() ale czy to też nie będzie dla mnie ślepa uliczka ?

Nie staram się, żeby program uruchamiał wiele wątków z obiektami ProcesModul. Wystarczy, że będzie to robił po jednej sztuce.
P-178635
pekfos
» 2021-05-20 19:52:58
Pasek progressbaru z funkcji ParseALargeFile() tworzę, żeby zobaczyć, że plik jest wczytywany.
Coś z nim jest nie tak, bo dochodzi do 10% i znika.
Jak Windows zauważy, że program jest wciąż zawieszony na ParseALargeFile, to przestanie aktualizować okno aplikacji. A potem sam usuwasz tego progrssbara.

Jeżeli funkcjonalności które chce osiągnąć przy użyciu wątków da się zrobić, to wystarczy mnie pchnąć w tym kierunku, będę starał się doedukować w tej tematyce i dopytywać.
Da się i musisz mieć wątek. Czemu kod roboczy wciąż nie ma wątku?

Czytałem o std::async() ale czy to też nie będzie dla mnie ślepa uliczka ?
Użyj zwykłego std::thread.
P-178636
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-20 20:31:14
Program raczej nie wiesza się na ParseALargeFile(), użyłem debugera i pętla do - while pracuje, tylko progresbar znika. Dla mnie nie stanowi to problemu, informacji o postępie wczytywania zamierzam użyć do iProgress jak widać. Może problem z paskiem jest taki, że gdy ta linia do linkera nie jest zakomentowana to progresbar jest w postaci kwadracików, a z tą linią to kawałek kolorowego pasku.

Nie drążyłem tematu wątków, bo sięgnąłem po literaturę:
"Skuteczny nowoczesny C++" Scott Meyers i tam w rozdziale 7 strona 293  jest napisane, żeby zamiast programowania opartego na wątkach stosować programowanie oparte na zadaniach. We wnioskach napisał coś takiego:

- Interfejs API std::thread nie oferuje bezpośredniego sposobu zwracania wartości z funkcji uruchamianych asynchronicznie, a jeśli te funkcje wywołują wątki, program kończy działanie.
- Programowanie oparte na wątkach wymaga ręcznego zarządzania wyczerpywaniem się wątków, przepełnieniem, równoważeniem obciążenia i dostosowywaniem do nowych platform.
- Programowanie oparte na zadaniach przy użyciu funkcji std::async z domyślną zasadą uruchomień rozwiązuje większość tych problemów.

Przykłady w tej książce są dość enigmatyczne więc robiłem testy na tym co znalazłem w sieci. Wnioski takie na szybko: funkcja std::async() z testowym obiektem zrobiła poprawnie to co miała zrobić, z tym że była kręcąca się klepsydra (nawet po tym jak program wygenerował plik). Oczywiście kręcąca się klepsydra zablokowała GUI

Dlatego pytałem o std::async() bo z wątkiem i join miałem podobny rezultat. Oczywiście nie zakładam, że zrobiłem wszystko poprawnie, bo to "poletko" jest dla mnie nowe.
P-178638
pekfos
» 2021-05-20 21:29:07
Program raczej nie wiesza się na ParseALargeFile(), użyłem debugera i pętla do - while pracuje
Z punktu widzenia Windowsa program jest zawieszony, jeżeli nie przetwarza komunikatów.

Nie drążyłem tematu wątków, bo sięgnąłem po literaturę
Ah tak, literatura..
a jeśli te funkcje wywołują wątki, program kończy działanie.
Dobrze to przepisałeś? Możesz tworzyć wątki z innych wątków.
C/C++
#include <cstdio>
#include <thread>
#include <Windows.h>

void fun( int n )
{
   
if( n > 0 )
   
{
       
std::thread t1( fun, n - 1 );
       
std::thread t2( fun, n - 1 );
       
t1.join();
       
t2.join();
   
}
   
else
   
{
       
for( int i = 0; i < 3; ++i )
       
{
           
printf( "Test %d\n", i );
           
Sleep( 1000 );
       
}
    }
}

int main()
{
   
fun( 4 );
}
Programowanie oparte na wątkach wymaga ręcznego zarządzania wyczerpywaniem się wątków, przepełnieniem, równoważeniem obciążenia i dostosowywaniem do nowych platform.
To już w ogóle interesujące. std::async() zgodnie z dokumentacją może tworzyć wątek dla każdego wywołania i dla takiego np kodu
C/C++
std::list < std::future < void >> futures;
for( int i = 0; i < 1000; ++i )
{
   
futures.push_back( std::async([ ]() { Sleep( 1000 ); } ) );
}
tworzy wszystkie 1000 wątków naraz. Z pewnością std::async() jest fajne, jeśli nie obchodzi Cię, jaki dokładnie będzie efekt. Według dokumentacji użyte wątki mogą pochodzić z puli wątków. W systemach które używają jednej puli, do której możesz się dostać przez mechanizm 'funkcji asynchronicznych' bywa, że biblioteki używają jej i istotne zadania dla działania programu są zagłodzone. Wtedy powinieneś utworzyć własną pulę wątków, wtedy wiesz dokładnie ile ich jest i co one robią. Przy projektowaniu rozmiaru puli trzeba też wziąć pod uwagę jakiego rodzaju zadania system ma wykonywać. Jeśli limitem jest CPU, to nie ma sensu iść w więcej niż ilość fizycznych rdzeni, a jeżeli limitem jest I/O, możesz z zyskiem przekraczać ilość rdzeni nawet wielokrotnie, bo chcesz wykorzystywać czas procesora kiedy inne wątki czekają na zakończenie nieobliczeniowych operacji. Trzeba też wziąć pod uwagę, czy masz do dyspozycji cały procesor, czy nie, bo jest to zwykły komputer, na którym użytkownik chce robić rzeczy w tle. Także moja dobra rada jest taka, żebyś użył zwykłego std::thread, bo to jest odpowiednie rozwiązanie do bieżącego problemu, według mojego rozumienia problemu, i w tym podejściu nie musisz się martwić o rzeczy które dzieją się za kulisami. Jedną z rzeczy które biorę pod uwagę jest to, że użytkownik może wrzucać do tego programu tyle ciężkich plików ile mu się podoba, licząc że zostaną zakolejkowane, podczas gdy std::async() tego nie zrobi i zetniesz komputer próbując przetwarzać wszystkie równolegle.

Dlatego pytałem o std::async() bo z wątkiem i join miałem podobny rezultat. Oczywiście nie zakładam, że zrobiłem wszystko poprawnie, bo to "poletko" jest dla mnie nowe.
Strzelam że nie zapisałeś sobie obiektu std::future, więc wątek od std::async() zadziałał tak, jakbyś użył na nim join().
P-178640
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-20 22:26:18
Tak na szybko, sprawdziłem jeszcze raz cytat z książki i zgadza się każde zaznaczone słowo. Mam kilkanaście pozycji książkowych w tej tematyce i gdy trafiam na problem to staram się go oczytać i ewentualne znaleźć rozwiązanie, lub chociaż kierunek w którym iść. Później przeszukuje internet. Może to nie najlepsze podejście, ale przy niezbyt niszowych problemach to sprawdzało się u mnie.

Co do maszyn na których uruchamiany będzie ten program to są to stacje robocze typu DELL7910 i podobne. Z Win7/Win10, 32MB Ram, XEONem z 12 rdzeniami(większość rdzeni wolna i do dyspozycji). Wątek może przygarnąć sobie cały fizyczny rdzeń i użytkownik nawet tego nie zauważy.

Progressbar to będzie kolejny krok jeżeli poradzę sobie z wątkiem, ale wrzuciłem go, żeby był szerszy pogląd na to co dzieje się na oknie GUI. Podejrzewam, że po drodze pojawi się jakiś kłopot z odświeżaniem ListView, ale to przyszłość.

Może nie wszystko zrozumiałem z powyższego wywodu o std::async(), ale dla mnie jest oczywiste, że powinienem próbować z std::thread
P-178641
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-20 22:26:18
P-178642
pekfos
» 2021-05-20 23:29:49
Wątek może przygarnąć sobie cały fizyczny rdzeń i użytkownik nawet tego nie zauważy.
Przy operacjach dyskowych jeden wątek raczej nie zajmie całego, najlepiej celować w kilka, ale z góry narzuconym limitem. Zresztą nawet jeżeli użytkownik nie zauważy że jeden rdzeń jest zawalony, to z dyskiem może być gorzej.
P-178643
idepozapalki
Temat założony przez niniejszego użytkownika
» 2021-05-21 16:04:38
Dyski są SSD. Robiłem drobne testy i wczytanie nawet 1GB pliku do RAMu szło dość sprawnie.

Popracowałem nad paskami. Teraz wygląda to tak:

C/C++
//---------------------------------------------------------
//
// Przykład z MSDN z plikiem wpisanym na stałe
//  rozmiar przykładowego pliku 94MB
//
//---------------------------------------------------------
BOOL ParseALargeFile( HWND hWnd )
{
   
RECT rcClient; // Client area of parent window.
   
HWND hwndPB; // Handle of progress bar.
   
HANDLE hFile; // Handle of file.
   
DWORD cb; // Size of file and count of bytes read.
   
LPCH pch; // Address of data read from file.
   
LPCH pchTmp; // Temporary pointer.
   
unsigned int wczytanychbajtow = 0; //
   
unsigned int filesize;
   
unsigned int progress; // do przechowywania postępu jako liczby całkowitej
    // Ensure that the common control DLL is loaded
   
GetClientRect( hWnd, & rcClient );
   
// dodaje pasek progressbaru na oknie
   
hwndPB = CreateWindowEx( 0, PROGRESS_CLASS,( LPTSTR ) NULL,
   
WS_CHILD | WS_VISIBLE, rcClient.left,
   
300, // położenie progressbaru w Y
   
rcClient.right,
   
30, // szerokość paska progressbaru
   
hWnd,( HMENU ) 0, hInst, NULL );
   
// Open the file for reading, and retrieve the size of the file.
    // ścieżka do pliku ustawiona na sztywno 95MB
   
hFile = CreateFile( L"C:\\1\\Testy4.txt", GENERIC_READ, FILE_SHARE_READ,
   
( LPSECURITY_ATTRIBUTES ) NULL, OPEN_EXISTING,
   
FILE_ATTRIBUTE_NORMAL,( HANDLE ) NULL );
   
if( hFile ==( HANDLE ) INVALID_HANDLE_VALUE )
       
 return FALSE;
   
   
cb = GetFileSize( hFile,( LPDWORD ) NULL ); // rozmiar pliku w bajtach
   
filesize = cb; // zapamiętuje rozmiar pliku
   
SendMessage( hwndPB, PBM_SETRANGE, 0,( LPARAM ) MAKELONG( 0, 100 ) ); // ustaw zakres pracy progressbaru na 0-100%
    // Parse the file.
   
pch =( LPCH ) LocalAlloc( LPTR, sizeof( char ) * 2048 );
   
pchTmp = pch;
   
do {
       
ReadFile( hFile, pchTmp, sizeof( char ) * 2048, & cb,( LPOVERLAPPED ) NULL );
       
wczytanychbajtow += cb; // zapamiętaj wczytaną liczbę bajtów
       
progress = std::ceil((( double ) wczytanychbajtow /( double ) filesize ) * 100 ); // ceil żeby nie było ewentualnych małych skoków paska do tyłu
        //progress = (unsigned int)(((double)wczytanychbajtow / (double)filesize) * 100);//przelicz na wartość procentową
       
StructureInVector.at( 0 ).iProgress = progress; // wpisz w strukturę
       
SendMessage( hwndPB, PBM_SETPOS,( WPARAM ) progress, 0 ); // pokaz na progressbarze
   
} while( cb );
   
   
CloseHandle(( HANDLE ) hFile ); // zamknij plik
   
DestroyWindow( hwndPB ); // usuń progressbar
   
return TRUE;
}

Windosowy progressbar wprawdzie nie zachowuje się do końca idealnie(praktycznie wygląda to tak jak gdyby rysowanie startowało odrobinę z lewej strony przed oknem aplikacji), ale do moich testów jest to wystarczające. Wyliczona procentowa wartość postępu zapisywana jest w strukturze.

Mam pytanie odnośnie wątku.
Czy mogę stworzyć go w części głównej jako SUSPENDED w taki sposób ?

myThread = (HANDLE)_beginthreadex(NULL, 0, &RunThread , (void*)i, CREATE_SUSPENDED, NULL);

Powód jest taki, że aplikacja może być uruchamiana z linkami do przetworzenia, ale oczywiście może być też uruchamiana bez nich. Pomyślałem, że wątek uśpiony będzie standardowo, a budzony (ResumeThread) gdy trafi się mu jakiś link.
P-178645
1 « 2 » 3 4
Poprzednia strona Strona 2 z 4 Następna strona