Progress Bar/Multithreading

Hello all,

Well, I suppose this is a common thing people try to accomplish - I want a progress bar in my main window to update as a lengthy calculation is taking place in its own thread. Below is the code I came up with. I thought it would work, but I cannot get the progress bar to update until after both the new threads complete. I also tried to use "SendMessage" instead of "PostMessage", but that completely freezes the program.

Can anyone make sense of this? This is my first attempt at multi-threading, so please suggest the best way to do this, no matter how similar or different it may be to what I tried. Thanks!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <windows.h>
#include <commctrl.h>
#include "resource.h"

//Info passed to DoOperation thread
typedef struct {
	int	*pProgress;
	HANDLE	hMutex;
} OPERATION_INFO;

//Info passed to UpdateProgress thread
typedef struct {
	int	*pProgress;
	HANDLE	hMutex;
	HWND	hwndProgress;
} PROGRESS_INFO;

void DoOperation(LPVOID lpThreadParams) {

	//Declare and initialize function variables
	OPERATION_INFO *pOperationInfo	= (OPERATION_INFO *)lpThreadParams;
	int *pProgress = (*pOperationInfo).pProgress; 
	HANDLE hMutex = (*pOperationInfo).hMutex;
	int i;

	//Run a test loop
	for (i=0; i<=100; i+=10) {
		//Wait for mutex
		WaitForSingleObject(hMutex,INFINITE);

		//Update the progress and simulate a portion of a lengthy calculation
		*pProgress = i;
		Sleep(500);
		
		//Release mutex
		ReleaseMutex(hMutex);
	}
}

void UpdateProgress(LPVOID lpThreadParams) {

	//Declare and initialize function variables
	PROGRESS_INFO *pProgressInfo = (PROGRESS_INFO *)lpThreadParams;
	int* pProgress = (*pProgressInfo).pProgress;
	HANDLE hMutex = (*pProgressInfo).hMutex;
	HWND hwndProgress = (*pProgressInfo).hwndProgress;

	//Monitor progress until complete
	while (*pProgress < 100) {

		//Wait for mutex
		WaitForSingleObject(hMutex,INFINITE);

		//Update the progress bar
		//SendMessage(hwndProgress,PBM_SETPOS,(WPARAM)*pProgress,0);
		PostMessage(hwndProgress,PBM_SETPOS,(WPARAM)*pProgress,0);

		//Release the mutex
		ReleaseMutex(hMutex);
	}

	//Finalize the progress
	//SendMessage(hwndProgress,PBM_SETPOS,100,0);
	PostMessage(hwndProgress,PBM_SETPOS,100,0);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

	static HWND	hwndStart, hwndProgress;
	static HANDLE	hMutex;
	static int	nProgress = 0;

	switch (message) {

		case WM_CREATE:
		{
			//Initialize progress controls
			INITCOMMONCONTROLSEX icce = { sizeof(INITCOMMONCONTROLSEX), ICC_PROGRESS_CLASS };
			InitCommonControlsEx(&icce);

			//Create the mutex
			hMutex = CreateMutex(NULL,FALSE,NULL);

			//Create the start button
			hwndStart = CreateWindow(
				"BUTTON", "Start", WS_CHILD | WS_VISIBLE, 300, 25, 50, 20, 
				hwnd, (HMENU)IDC_START, NULL, NULL
			);
			
			//Create the progress control
			hwndProgress = CreateWindow(
				PROGRESS_CLASS,NULL,WS_CHILD|WS_VISIBLE|PBS_SMOOTH, 25,25,200,20,
				hwnd, (HMENU)IDC_PROGRESS, NULL, NULL
			);

			//Set the progress bar range and initial position
			SendMessage(hwndProgress,PBM_SETRANGE32,0,100);
			SendMessage(hwndProgress,PBM_SETPOS,0,0);
			break;
		}
		
		case WM_COMMAND:
			if (LOWORD(wParam) == IDC_START) {
				//Declare and initialize thread variables
				OPERATION_INFO operationInfo = { &nProgress, hMutex };
				PROGRESS_INFO  progressInfo  = { &nProgress, hMutex, hwndProgress };
				HANDLE threads[2];

				//Begin threads
				threads[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DoOperation,(LPVOID)&operationInfo,0,0);
				threads[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)UpdateProgress,(LPVOID)&progressInfo,0,0);

				//Wait for threads to finish
				WaitForMultipleObjects(2,threads,TRUE,INFINITE);

				//Clean up
				CloseHandle(threads[0]);
				CloseHandle(threads[1]);
			}
			break;

		case WM_CLOSE:
			PostQuitMessage(0);
			break;
	
		default:
			return DefWindowProc(hwnd, message, wParam, lParam);
	}

	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {

	MSG msg;
	HWND hwnd;
	WNDCLASSEX wcex = {0};

	wcex.cbSize		= sizeof(WNDCLASSEX);
	wcex.lpfnWndProc	= WndProc;
	wcex.hInstance		= hInstance;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszClassName	= "MAIN";
	wcex.hIconSm		= LoadIcon(NULL,IDC_ARROW);

	RegisterClassEx(&wcex);

	hwnd = CreateWindow(
		"MAIN", "Progress Bar Test App", WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, 400, 100, NULL, NULL, hInstance, NULL
	);

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int) msg.wParam;
}
Last edited on
Oh that it so messy - it can be done using one thread.
This is a working version - I reckon we could still clean it up a bit more:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include <windows.h>
#include <commctrl.h>
#include "resource.h"

//Info passed to DoOperation thread
typedef struct {
	int	*pProgress;
	HANDLE	hMutex;
} OPERATION_INFO;

//Info passed to UpdateProgress thread
typedef struct {
	int	*pProgress;
	HANDLE	hMutex;
	HWND	hwndProgress;
} PROGRESS_INFO;

void DoOperation(LPVOID lpThreadParams) {

	//Declare and initialize function variables
	OPERATION_INFO *pOperationInfo	= (OPERATION_INFO *)lpThreadParams;
	int *pProgress = (*pOperationInfo).pProgress; 
	HANDLE hMutex = (*pOperationInfo).hMutex;
	int i;

	//Run a test loop
	for (i=0; i<=100; i+=10) {

		//Update the progress and simulate a portion of a lengthy calculation
		*pProgress = i;
		Sleep(500);
		
	}
}



LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

	static HWND	hwndStart, hwndProgress;
	static HANDLE	hMutex;
	static int	nProgress = 0;

	switch (message) {

		case WM_CREATE:
		{
			//Initialize progress controls
			INITCOMMONCONTROLSEX icce = { sizeof(INITCOMMONCONTROLSEX), ICC_PROGRESS_CLASS };
			InitCommonControlsEx(&icce);

			//Create the mutex
			hMutex = CreateMutex(NULL,FALSE,NULL);

			//Create the start button
			hwndStart = CreateWindow(
				"BUTTON", "Start", WS_CHILD | WS_VISIBLE, 300, 25, 50, 20, 
				hwnd, (HMENU)IDC_START, NULL, NULL
			);
			
			//Create the progress control
			hwndProgress = CreateWindow(
				PROGRESS_CLASS,NULL,WS_CHILD|WS_VISIBLE|PBS_SMOOTH, 25,25,200,20,
				hwnd, (HMENU)IDC_PROGRESS, NULL, NULL
			);

			//Set the progress bar range and initial position
			SendMessage(hwndProgress,PBM_SETRANGE32,0,100);
			SendMessage(hwndProgress,PBM_SETPOS,0,0);
			break;
		}
		
		case WM_COMMAND:
			if (LOWORD(wParam) == IDC_START) {
				//Declare and initialize thread variables
				OPERATION_INFO operationInfo = { &nProgress, hMutex };
				PROGRESS_INFO  progressInfo  = { &nProgress, hMutex, hwndProgress };
				
        
        HANDLE threadHandle; //** ONLY ONE THREAD NOW

				//Begin threads
				threadHandle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DoOperation,(LPVOID)&operationInfo,0,0);

				//Wait for threads to finish - update while  while not finished
        int progressVal;
        do 
        {
          progressVal = * progressInfo.pProgress;
				  SendMessage(hwndProgress,PBM_SETPOS,(WPARAM)progressVal,0); 
          ;
        }while( WaitForSingleObject(threadHandle,100)!= WAIT_OBJECT_0 );

				//Clean up
				CloseHandle(threadHandle);
			}
			break;

		case WM_CLOSE:
			PostQuitMessage(0);
			break;
	
		default:
			return DefWindowProc(hwnd, message, wParam, lParam);
	}

	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {

	MSG msg;
	HWND hwnd;
	WNDCLASSEX wcex = {0};

	wcex.cbSize		= sizeof(WNDCLASSEX);
	wcex.lpfnWndProc	= WndProc;
	wcex.hInstance		= hInstance;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszClassName	= "MAIN";
	wcex.hIconSm		= LoadIcon(NULL,IDC_ARROW);

	RegisterClassEx(&wcex);

	hwnd = CreateWindow(
		"MAIN", "Progress Bar Test App", WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, 400, 100, NULL, NULL, hInstance, NULL
	);

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int) msg.wParam;
}
In reality though, your original programme (and my updated version) may have one or more threads, BUT the main thread still blocks waiting for these child threads to complete.
So the threading possibility is not used to it's fullest potential.
So really, it could all have been done in the main thread.


While I'm still thinking about it, in you original post, the Update progress thread was trying to send/post a
message to the progress window, which lives in the main thread, but the main thread was blocked on
WaitforMultipleObjects function - I think that was one of the reasons the thing didn't function correctly (locked up) and the progress bar didn't update until all threads were completed and the main thread got released again.
Thank you - I appreciate the response more than you know - I'm sure many people aren't eager to sift through big chunks of code like that.

Your version works exactly as I intended - I had suspected maybe I only one thread was necessary...

I now understand why the main thread was being blocked out (one of those things that now seems completely obvious to me)... but I am still confused about the whole point of using the mutex - without using it in the window procedure, isn't there a possibility my UI thread will try to access the progress value when the worker thread is updating it?
isn't there a possibility my UI thread will try to access the progress value when the worker thread is updating it?
.

yes, but in this particular program (operation thread writes and main thread only reads), that wouldn't matter - so the main thread may update the progress bar 1 millisecond late it is no big deal in this case.
I think a mutex to synchronize the changing of the progress bar value is over-kill (somebody may come along and disagree)


My feelings are (I 'm going to try these out):
If the main thread must not be allowed to continue until the operation is completed - then the you might as well put the operation in the main thread.

If the main thread can be allowed to continue - move the updating of the progress bar into the operation thread
and periodically check in the main thread (possibly by use of a timer ) whether the thread has completed.
Last edited on
Ok, so the version you were given is not very good either because it forces the main thread into a loop.

The cleanest way is to send a user-defined message to the main thread with the progress information, and getting rid of the do loop you were given.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

#define WM_UPDATEPROGRESS WM_USER + 1

void DoOperation(LPVOID lpThreadParams) {

	//Declare and initialize function variables
	OPERATION_INFO *pOperationInfo	= (OPERATION_INFO *)lpThreadParams;
	int *pProgress = (*pOperationInfo).pProgress; 
	HANDLE hMutex = (*pOperationInfo).hMutex;
	int i;

	//Run a test loop
	for (i=0; i<=100; i+=10) {
		Sleep(500);
		//Needs to be dynamically allocated to allow the main thread to clean up safely.
		PROGRESS_INFO *pInfo = new PROGRESS_INFO();
		pInfo->int = i;
		//I don't need the other field members.
		PostMessage(mainhWnd, WM_UPDATEPROGRESS, 0, pInfo);
	}
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

	static HWND	hwndStart, hwndProgress;
	static HANDLE	hMutex;
	static int	nProgress = 0;

	switch (message) {

		case WM_CREATE:
		{
			//Initialize progress controls
			INITCOMMONCONTROLSEX icce = { sizeof(INITCOMMONCONTROLSEX), ICC_PROGRESS_CLASS };
			InitCommonControlsEx(&icce);

			//Create the mutex
			hMutex = CreateMutex(NULL,FALSE,NULL);

			//Create the start button
			hwndStart = CreateWindow(
				"BUTTON", "Start", WS_CHILD | WS_VISIBLE, 300, 25, 50, 20, 
				hwnd, (HMENU)IDC_START, NULL, NULL
			);
			
			//Create the progress control
			hwndProgress = CreateWindow(
				PROGRESS_CLASS,NULL,WS_CHILD|WS_VISIBLE|PBS_SMOOTH, 25,25,200,20,
				hwnd, (HMENU)IDC_PROGRESS, NULL, NULL
			);

			//Set the progress bar range and initial position
			SendMessage(hwndProgress,PBM_SETRANGE32,0,100);
			SendMessage(hwndProgress,PBM_SETPOS,0,0);
			break;
		}
		
		case WM_COMMAND:
			if (LOWORD(wParam) == IDC_START) {
				//Declare and initialize thread variables
				OPERATION_INFO operationInfo = { &nProgress, hMutex };
				PROGRESS_INFO  progressInfo  = { &nProgress, hMutex, hwndProgress };
				
        
        HANDLE threadHandle; //** ONLY ONE THREAD NOW

				//Begin threads
				threadHandle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)DoOperation,(LPVOID)&operationInfo,0,0);
			break;

		case WM_CLOSE:
			PostQuitMessage(0);
			break;
	
		case WM_UPDATEPROGRESS:
			PROGRESS_INFO pInfo = reinterpret_cast<PROGRESS_INFO*>(lParam);
			//Update the progress bar with pInfo->int here.
			//After updating, delete the PROGRESS_INFO structure as it is no longer needed.
			delete pInfo;
			break;

		default:
			return DefWindowProc(hwnd, message, wParam, lParam);
	}

	return 0;
}


As you can see, the worker function notifies the main thread about its progress using a user-defined windows message. The main thread then handles this message and frees the memory allocated for the lParam parameter.

This does not require a use of a mutex, or critical section or any other synchronization object because the main thread is accessed via the message queue, and that is self-synchronizing.

Note that I post the message using a mainhWnd variable. Replace that with a variable that contains the main window handle.

You have an HWND field member in PROGRESS_INFO. That would be useful in cases where you had more than one progress bar. Otherwise don't use it, in my opinion.

Finally, I don't use the mutex member field either. As I stated before, you don't need a synchronization object for this particular setup.
Ok, so the version you were given is not very good either because it forces the main thread into a loop.


I know that it puts the main thread in a loop. That's why I made those comments at the end of my post.
My bad guestgulkan, I missed it.
Topic archived. No new replies allowed.