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.
#include <process.h> #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 ); }
};
std::list < STask > g_TaskQueue;
void __cdecl ThreadProc( void * Args ) {
while( !g_TaskQueue.empty() ) {
EnterCriticalSection( & g_Section ); STask task = g_TaskQueue.back();
std::cout << "Watek nr" << task.taskType << std::endl;
g_TaskQueue.pop_back();
LeaveCriticalSection( & g_Section ); task.Process();
} _endthread();
}
int main() {
InitializeCriticalSection( & g_Section );
STask task { 0, 0, 0, "Some text" };
for( int i = 0; i < 10; ++i ) {
task.taskType = i; g_TaskQueue.push_back( task );
}
HANDLE hThread =( HANDLE ) _beginthread( ThreadProc, 0, NULL );
WaitForSingleObject( hThread, INFINITE );
std::cout << "Watek zakonczyl dzialanie." << std::endl;
DeleteCriticalSection;
}
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:
#include <windows.h>
#include <commctrl.h>
#include <string>
#include <thread>
#include <process.h> #include <vector>
#pragma comment(lib,"comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version = '6.0.0.0' processorArchitecture = '*' publicKeyToken = '6595b64144ccf1df' language = '*'\"")
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"; struct Task {
int ElementNr = 0; std::wstring path; std::wstring Produkt; int iProgress = 0; };
static std::vector < Task > StructureInVector; 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();
}
BOOL ParseALargeFile( HWND hWnd )
{
RECT rcClient; int cyVScroll; HWND hwndPB; HANDLE hFile; DWORD cb; DWORD filesize;
LPCH pch; LPCH pchTmp; GetClientRect( hWnd, & rcClient );
hwndPB = CreateWindowEx( 0, PROGRESS_CLASS,( LPTSTR ) NULL,
WS_CHILD | WS_VISIBLE, rcClient.left,
300, rcClient.right,
30, hWnd,( HMENU ) 0, hInst, NULL );
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 ); filesize = cb;
SendMessage( hwndPB, PBM_SETRANGE, 0, MAKELPARAM( 0, cb / 2048 ) ); SendMessage( hwndPB, PBM_SETSTEP,( WPARAM ) 1, 0 ); pch =( LPCH ) LocalAlloc( LPTR, sizeof( char ) * 2048 );
pchTmp = pch;
int tsize;
int bsize;
do {
ReadFile( hFile, pchTmp, sizeof( char ) * 2048, & cb,( LPOVERLAPPED ) NULL );
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;
}
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 ]; if( !StructureInVector.empty() ) { col.mask = LVIF_PARAM | LVIF_TEXT;
col.iItem = i;
col.iSubItem = 0;
col.pszText = LPTSTR(( LPCTSTR )( StructureInVector.at( i ).path ).c_str() );
col.lParam = StructureInVector.at( i ).iProgress; SendMessageW( hwndListView, LVM_INSERTITEM, 0,( LPARAM ) & col );
col.mask = LVIF_TEXT;
col.iSubItem = 1;
col.pszText = LPTSTR(( LPCTSTR )( StructureInVector.at( i ).Produkt ).c_str() );
SendMessageW( hwndListView, LVM_SETITEM, 0,( LPARAM ) & col );
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:
return CDRF_DODEFAULT | CDRF_NOTIFYITEMDRAW;
case( CDDS_ITEM | CDDS_PREPAINT )
: return CDRF_DODEFAULT | CDRF_NOTIFYSUBITEMDRAW;
case( CDDS_ITEM | CDDS_SUBITEM | CDDS_PREPAINT )
: switch( pcd->iSubItem )
{
case 1:
col.iSubItem = pcd->iSubItem;
col.pszText = buffer;
col.cchTextMax = sizeof( buffer ) / sizeof( buffer[ 0 ] );
SendMessage( hwndListView, LVM_GETITEMTEXT, pcd->nmcd.dwItemSpec,( LPARAM ) & col ); return CDRF_DODEFAULT;
case 2:
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 ); hBackBrush = CreateSolidBrush( clrBack );
hProgressBrush = CreateSolidBrush( RGB( 190, 190, 255 ) ); hPen = CreatePen( PS_SOLID, 0, RGB( 190, 190, 255 ) ); 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; 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 ); 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 ); col.iSubItem = pcd->iSubItem;
col.pszText = buffer;
col.cchTextMax = sizeof( buffer ) / sizeof( buffer[ 0 ] );
SendMessage( hwndListView, LVM_GETITEMTEXT, pcd->nmcd.dwItemSpec,( LPARAM ) & col ); 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 );
return CDRF_SKIPDEFAULT; }
break;
}
return CDRF_DODEFAULT;
}
static void CreateListViewLabels() {
LVCOLUMN col;
col.mask = LVCF_WIDTH | LVCF_TEXT;
col.pszText =( LPWSTR ) L"Przetwarzany plik";
col.cx = 265;
SendMessageW( hwndListView, LVM_INSERTCOLUMN, 0,( LPARAM ) & col );
col.pszText =( LPWSTR ) L"Produkt";
col.cx = 60;
SendMessageW( hwndListView, LVM_INSERTCOLUMN, 1,( LPARAM ) & col );
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 );
}
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;
}
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,
650,
400,
NULL,
NULL,
hInst,
NULL );
}
void CreateCommonControls() {
INITCOMMONCONTROLSEX cc;
cc.dwSize = sizeof( INITCOMMONCONTROLSEX );
cc.dwICC = ICC_PROGRESS_CLASS;
cc.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx( & cc );
}
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 );
}
CreateCommonControls();
Task t { 0, fileName, L"Produkt2", 8 }; StructureInVector.push_back( t ); AddNewItemOnListView( 0 );
ShowWindow( hWnd, showCmd ); UpdateWindow( hWnd ); ParseALargeFile( hWnd );
ZeroMemory( & msg, sizeof( MSG ) );
while( msg.message != WM_QUIT ) { if( PeekMessage( & msg, 0, 0, 0, PM_REMOVE ) ) {
TranslateMessage( & msg ); DispatchMessage( & msg ); }
}
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.