Reputation: 867
What is the best way for worker threads to communicate with the main UI thread?
Summary: My C++/MFC application is dialog-based. To do lengthy computations, the main UI thread creates several worker threads. As the worker threads progress in the calculation, they report their progress to the main UI thread, which then displays progress.
This works fine for numeric progress values, which are in shared memory (written by workers, read by UI), but I'm having trouble with text progress messages. My attempted solutions have been through several iterations, and none seems to work.
I had the UI thread pass pointers to controls to the workers, and the workers updated the UI directly. This wasn't very effective, and seems like the wrong approach.
I had the workers send messages, using SendMessage to the UI thread's window. This deadlocked. (SendMessage doesn't return until the message has been processed.)
Same as (2), except using PostMessage to the UI thread's window. This worked, for a while, then messages got lost. (PostMessage returns immediately.) Further investigation revealed the quota for message queues, which defaults to 10,000, was being exceeded.
I increased the quota for message queues (variable HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERPostMessageLimit in the registry), but the number of lost messages didn't change.
I had each worker thread buffer messages in 4 KByte buffers, and PostMessage when a buffer filled. This failed because the UI thread never received any messages. Same was true when I increased the buffer size to 64 KBytes.
The worker threads are running at "lowest" priority, and the UI thread at "normal" priority. Worker threads are sending messages with code like
UIMessage *pUI=new UIMessage; // so it won't go out of scope (main dialog will delete it)
pUI->buffer=traceLineBuffer; pUI->nOutputs=traceN;
BOOL ok=::PostMessage(hWndMainDlg,TraceLineMsg,(WPARAM)pUI, NULL/*lParam*/);
and UI is receiving them with code like
BEGIN_MESSAGE_MAP(CMainDlg, CDialog)
...
ON_MESSAGE(TraceLineMsg,OnTraceLineMsg)
...
END_MESSAGE_MAP()
LRESULT CMainDlg::OnTraceLineMsg(WPARAM wParam, LPARAM lParam)
{
UIMessage *pUI=(UIMessage *)wParam;
char *p=pUI->buffer;
// PROCESS BUFFER
delete[] pUI->buffer;
delete pUI;
return 0;
}
Questions:
What is the preferred way for workers to issue progress reports, in a case where there may be bursts of several thousand text reports?
Why can I not increase the quota of post messages in a queue?
Why does the main UI thread seemingly never receive the messages in the buffers, even though the mechanism for transmitting them was identical to posting the individual reports?
64-bit Windows 7, Visual Studio 2010, native C++/MFC
Upvotes: 5
Views: 6106
Reputation: 7414
MFC worker threads on Windows have multiple options for communicating with the main thread. You have the standard thread signaling and synchronization primitives (mutex, semaphore, event), the easy to use PostMessage, and the higher performance I/O Completion Port mechanism.
// syncronization
{
CSingleLock lock(&sharedCriticalSection,TRUE);
sharedList.push_back(msg);
}
// other thread(s) are blocked/pending or you send an event or message to signal
// messages
Data* data = new Data(payload);
PostMessage(hWnd, REGISTERED_MESSAGE, 0, (LPARAM)data);
// target window handles message and deletes data
// if it is not blocked or too slow and the queue overflows
// skipping lots of IO completion port boilerplate and showing the key methods
messagePort = CreateIoCompletionPort(...);
...
GetQueuedCompletionStatus(messagePort,...);
...
PostQueuedCompletionStatus(messagePort,...);
None of them will get much done or increase your performance or responsiveness if you block or busy-wait for thread completion.
Comments on your observations:
Answers to your questions:
Upvotes: 2
Reputation: 24847
There is not much point in issuing progress reports on the GUI any faster than they can be assimilated by the user. When there is a very large amount of activity in the other threads, it's common to poll the progress vars in the threads using a GUI timer and so update the GUI controls every, say 500ms.
This is one of those very few times when timer-polling is actually advantageous. You get peridoc progress reports without the danger of stuffing the GUI Windows message queue with updates. For example, uTorrent client, (where there is a huge amount of network activity), uses this scheme - trying to update the GUI download stats on every network protocol unit received would surely stuff the GUI.
You buffering scheme in (5) should have worked. I frequently transfer large data items to the main GUI thread by loading an object pointer into LPARAM or WPARAM, new'ing them in the work thread and delete'ing them in the GUI after display. Your (5) should have worked and, at least reduced the overhead of progress data transfer. I can only assume that the amount of data to be displayed was still too large and so the GUI thread could still not keep up :(
Upvotes: 2
Reputation: 10415
With the main thread in a WaitForMultipleObjects call no messages will be processed and no controls or other windows can be updated. The solution is: Don't do that.
Upvotes: 4