Interesting Multithreading Problem :: MFC

kuphryn

Senior member
Jan 7, 2001
400
0
0
Hi.

I am stuck in the design process of a small program I am working on. Here is the scenario.

-----
- user select an option in the menu
- main thread creates a dialog box w/ process bar
- dialog box's OnInitDialog() sends a message to main frame
- main frame redirects to view
- view creates a worker thread
- worker thread goes through a for loop // (i = 0, 1 < 10000; ++i);
- worker thread posts a message to main frame on every iteration
- main frame redirects message to view
- view calls a function in dialog box to update process bar
-----

Okay. The design above works fine. Here is one drawback. Even though the worker thread does all the processing (for loop), the main thread is *inaccessible* as it calls the function in the dialog box to update the process bar.

I would like to redesign that part of the program so that even as the worker thread is processing the for loop and as *some thread* updates the process bar, the user can still navigate the program.

Do I need to create a worker thread to update the progress bar? Do I need a UI thread? The reason I kept the dialog box as part of main thread is because according to Prosise, it is best to keep UI related objects in main thread.

Thanks,
Kuphryn
 

Adrian Tung

Golden Member
Oct 10, 1999
1,370
1
0
Just one of the ideas that are on top of my head at the moment... Why don't you create the progress bar as a modeless dialog instead? Totally separate it from the frame and view and let the worker thread directly access the modeless dialog.



:)atwl
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
Thanks.

Yes, the dialog box is modeless. However, it is declared as private in view. I can include a pointer to it in structure that is passed to the thread function. There is one problem, or rather, uncertainty. The uncertainty is if the worker thread is designed to do process one job, I do not know if I should have it update the dialog box.

Nonetheless, you made a very good pointer. I will definitely try that. Thanks.

Kuphryn
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
Okay. I want to clarify the actual problem. I saw a response from a member that implied that my description was not easy to understand.

The design works fine.

- worker thread handles data processing
- main thread (view) sends messages to dialog box
- dialog box updates the progress bar

The problem is that I believe the process of updating the progress bar is *considered* as part of the main thread. I think the reason is because the dialog box itself is declared in view (main thread).

I need three *independent* processes.

1) data processing
2) dialog box/progress bar updating
3) main thread is idling thus keeps the program from "locking."

Problem: Program seems to "lock" even though the data processing is done in the worker thread.

Question: I believe the reason it seems to "lock" is because as main thread sends message to dialog box to update the progress bar and as the dialog box update the progress bar, the frame considers the entire process (message + updating) as being in the main thread. I need to know a way to make everything separate.

Kuphryn
 

singh

Golden Member
Jul 5, 2001
1,449
0
0
Is this an SDI App? I think I may have an example app for you if it is.
 

singh

Golden Member
Jul 5, 2001
1,449
0
0
Reading further, I see no reason why you need more than two threads. The application should never freeze unless you stop processing Windows messages - and that should happen only if you spend considerable time in one function and never return control to Windows. Since all the work is being done in your Worker thread, there is no reason for the app to freeze.

One way I did this a while ago was to keep a global variable that stored the progress of the Worker thread. Then, a timer loop was set up in the Progress dialog (firing say, five times a second). When the timer expires, just update the progress bar with the value of the global variable. If an error occurs, the global variable is set to an invalid vale (say < 0), and an appropriate global string is updated as to the cause.

The above design is by no means elegant, but it is practical for very small applications. For a more elegant and "proper" solution, what you propose is better, but it is more complicated to code.
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
Okay. Thanks.

The technique I propose is okay, but not the best way either. One reason is that the worker thread sends the mainframe messages each time the dialog box should update the progress bar. Here is the formula for the position of the progress bar.

// position = (current * 100) / total

You guessed it! If total is, say, 10000, that means that the worker thread will send 10000 messages to the mainframe. That is definitely a huge waste of resource. Nonetheless, the program seems to work fast enough.

Kuphryn
 

singh

Golden Member
Jul 5, 2001
1,449
0
0
No, your update to the main view should be based on a PERCENTAGE, not an absolute value. So, at max you should send 100 messages for 100%.
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
No.

Let say, for example, that I worker thread processes a for loop.

// for (int i = 0; i < 10000; ++i)
{
...
AfxGetMainWnd()->PostMessage(WM_USER_UPDATE, i, 10000);
}

In the receiver side,

// LRESULT OnUpdate(WPARAM wparam, LPARAM lparam);
{
position = (static_cast<int>(wparam) * 100) / (static_cast<int>(lparam));

The code above is one example.

Do you see what I mean now? The worker thread does in fact send main 10000 messages.

Kuphryn
 

singh

Golden Member
Jul 5, 2001
1,449
0
0
Only because you're making it to that. Why not compute the PERCENTAGE DONE in the worker thread; check to see if it's the same as the last percentage, and if it has changed, send the new percentage to the main window. What's so complicated about that?

Also, practically speaking, you will ALWAYS compute the amount of work done in the worker thread, not in the progress window. It's bad design to make the progress window compute the work because it is ONLY responsible for updating the amount of work done, and not computing how much work was done. That should be passed from the worker thread.
 

singh

Golden Member
Jul 5, 2001
1,449
0
0
As an example:


In the worker thread:

p_done = 0; /* Current percentage done */
last_p_done = 0; /* Last percentage done */
while(!bDone)
{
...

p_done = (done / total) * 100; /* Current Percentage done */
if(p_done != last_p_done)
{
AfxGetMainWnd->PostMessage(WM_USER_UPDATE, p_done, 0);
}
last_p_done = p_done;

}

In Main Wnd:
position = (int)wparam;
/* Update progress control with position */


 

kuphryn

Senior member
Jan 7, 2001
400
0
0
Nice! Thanks. I appreciate your thorough explanation.

That technique is invaluable to me! Prosise uses s similar technique in his book, but he passes both values via WPARAM and LPARAM.

For example:

// position = (current * 100) / total;

Prosises passes current through WPARAM and total through LPARAM. I got confused because of that. You cleared it all up.

Kuphryn
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
Okay.

I have consider several different design schemes. One of which is passing a pointer of a dialog box created in view into the worker thread. There, the worker thread can update the dialog box's progress bar. However, I do not think that will solve the problem of "locking." The reason is the dialog box belongs to view, thus it is still apart of view and the main thread. That leaves me with two alternatives opposites of one another.

Alternative #1
- create the dialog box first
- message view after InitDialog()
- view active a function inside of the dialog box
- function instantiate a worker thread that is *part of dialog box*
- worker thread will update the progress bar directly

Alternative #2 (opposite #1)
- create a worker thread
- create a dialog box w/ progress bar *inside of worker thread*
- update the progress bar directly

From the two alternatives, which one do you think is best? I want to make the design as "safe" as possible i.e, non-volatile.

Please mention any ideas you may have.

Thanks,
Kuphryn
 

singh

Golden Member
Jul 5, 2001
1,449
0
0
It doesn't matter where you create the worker thread, because by definition, it is a different thread and as such it has nothing to do with the main application.

Let's say you wanted to show a progress bar for uploading a file to an FTP server. Let's assume the worker thread is responsible for doing that. The thread will accept as input parameter the HWND (RAW Windows window handle) of a window so that it can POST progress messages to the window you specify. It would also need the local file name, remote file name, and IP address + user name + password for the server. For posting the messages, it can be as simple as posting the percentage done. If an error occurs, the thread will post a NEGATIVE number as progress to indicate an error. Further, a string pointer explaining the message can be set in LPARAM. Now, the progress dialog will accept as a parameter a pointer to the Thread function which will be started in OnInitDialog() of the progress dialog.

Suppose that the progress dialog is called CFTPProgressDialog, and the thread function FTPTransferThread, just:

CFTPProgressDialog Dlg;
Dlg.SetThreadFunc(FTPTransferThread);
... // Set other info
Dlg.DoModal();

The above can be done as a response to a menu handler for example. That should take care of all problems. There will never be a "lock-up" because Windows will keep pumping messages after you call DoModal(). The only remaining problem is when the user wants to cancel the upload. There are many ways to handle that. It could be as simple as a boolean variable that you can set which will tell the thread to cancel upload.

I should also mention that Threads themselves can also have message Queues associated with them, and you can use API functions such as PostThreadMessage() to communicate.
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
I never said anything about where the worker thread was created. I mentioned "passing a pointer of a dialog box created in view into the worker thread" and that "The reason is the dialog box belongs to view, thus it is still apart of view and the main thread."

Stoffel brought up a good point that it is not possible to operate a UI object outside of the UI class where you created it.

http://www.gamedev.net/community/forums/topic.asp?topic_id=91304

Another person asked if maybe I have included a Sleep(), which could cause the program to appear "locked." I honestly do not remember. I do remember using Sleep() to test the program during implementation as a test. I will look over the code and make sure there is not call to Sleep().

Kuphryn
 

Adrian Tung

Golden Member
Oct 10, 1999
1,370
1
0
It doesn't matter where you create the progress bar, as long as it is modeless it shouldn't lock your app or cause any locking problem at all. How are you creating and initializing your progress bar window anyway?


:)atwl
 

kuphryn

Senior member
Jan 7, 2001
400
0
0
The dialog box is a private member object of the view. View creates the it when the user chooses an option in the menu. View calls a function in doc that begins the worker thread after the dialog box notifies view that it has been initiated and is ready. In general, view is the gateway between the worker thread in doc and the progress bar in the dialog box.

The program seem faster after I removed the Sleep() I have put in as a test in the beginning of the implementatio process to debug the program.

I am always open to new design.

Kuphryn