Measurement Studio for VC++

cancel
Showing results for 
Search instead for 
Did you mean: 

CoInitialize with CNiGraph

I'm currently using a CNiGraph object on an dialog box. When a user hits the start button, I begin a worker thread which, as its first line, calls CoInitialize(NULL);

At one time, within the function creating the thread, I would call ::WaitForSingleObject. However, upon reading the MSDN documentation on CoInitialize, M$ instructs to use ::MsgWaitForMultipleObjects when the CoInitialize() function is called as an infinite wait period will cause the application to deadlock on itself.

The call seemed to work, HOWEVER, I can no longer retrieve the exit code of my thread correctly and my program goes into deadlock. A simple example of this is the following

void CCalibrate::OnBnClickedCalibrate()
{
CWinThread *thread = AfxBeginThread(
node_streaming_thread, this);
thread->m_bAutoDelete = FALSE;
while (mStreaming)
AfxGetApp()->PumpMessage();
::MsgWaitForMultipleObjects(1, &thread->m_hThread, FALSE, INFINITE, QS_ALLEVENTS);
DWORD exitCode;
GetExitCodeThread(thread->m_hThread, &exitCode);
TRACE("Exit Code: %i\n", exitCode);
CloseHandle(thread->m_hThread);
}

The thread call looks similar to the following:

UINT node_streaming_thread(LPVOID lpParam) // streaming thread
{
CoInitialize(NULL);
// Random graph drawing
mStreaming = false;
return 0;
}

For the time being, assume mStreaming is global. This isn't true, but cuts out useless lines of code for the example.

In any sense, I can understand why I can't retrieve the exit code of the thread. However, I'm wondering if there is, in fact, anyway to obtain that exit code and not cause, what I'm lead to believe, is a deadlock between the thread and waiting for a screen update.

TIA


System Stats:
Win XP SP 1
VS .NET 2k3
Measurment Studio 7.0.0.341
0 Kudos
Message 1 of 2
(3,560 Views)
First let me clear up some apparent confusion over MsgWaitForMultipleObjects and CoInitialize. You must use MsgWaitForMultipleObjects from within any thread that calls CoInitialize. In your case, you are calling CoInitialize from within the worker thread and you are waiting from the main thread. So, the call you are explicitly making is not the reason you need to use MsgWaitForMultipleObjects. MFC calls CoInitialize on your application's main thread during startup. It is this call, within the guts of MFC, that necessitates the use of MsgWaitForMultipleObjects.

The reason that you must use MsgWaitForMultipleObjects from threads that call CoInitialize is that CoInitialize causes a window, which is used by COM, to be created. Windows messages sent to that window must be processed to prevent deadlock and to make COM objects, such as the graph in this case, work properly. The thread that created the window must process those messages by pulling them from the thread's message queue, using either GetMessage or PeekMessage. MsgWaitForMultipleObjects returns either when the object you are waiting for is signaled OR when a message is in the calling thread's message queue. You use the dwWakeMask parameter to MsgWaitForMultipleObjects to specify which types of messages will cause MsgWaitForMultipleObjects to return.

In your case, it looks like you are assuming the MsgWaitForMultipleObjects is returning because the thread has finished. In fact, it is returning because there are messages in the main thread's queue that must be processed. You can prove this by replacing your call to MsgWaitForMultipleObjects with the following code:

DWORD result = ::MsgWaitForMultipleObjects(1, &thread->m_hThread, FALSE, INFINITE, QS_ALLEVENTS);
if (result == WAIT_OBJECT_0)
TRACE("Thread complete\n");
else
TRACE("Something else signaled: %d\n", result);

When I did this, I saw "Something else signaled" in my debug output window.

To fix this, you need to process all of the messages that cause MsgWaitForMultipleObjects to return. In your case, you want to do this until the worker thread has finished execution. To do this, you create a loop in which you process all messages and then check for the thread exiting. Only when the thread exits do you exit your loop. This is explained somewhat in the topic Waiting in a Message Loop that is linked from the MsgWaitForMultipleObjects function reference.

Here is a possible implementation of your button clicked function with such a loop:

void CCalibrate::OnBnClickedCalibrate()
{
std::auto_ptr thread(AfxBeginThread(node_streaming_thread, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED));
thread->m_bAutoDelete = FALSE;
thread->ResumeThread();

while (true)
{
MSG msg;
while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
DWORD result = ::MsgWaitForMultipleObjects(1, &thread->m_hThread, FALSE, INFINITE, QS_ALLEVENTS);
if (result == WAIT_OBJECT_0)
{
TRACE("Thread complete\n");
break;
}
else
TRACE("Something else signaled: %d\n", result);
}

DWORD exitCode;
GetExitCodeThread(thread->m_hThread, &exitCode);
TRACE("Exit Code: %i\n", exitCode);
}

This should work for you. Let me point out a couple of additional things about this implementation:

1) I don't use the mStreaming flag. Instead, I just use the thread termination as my signal that it is finished. You might have another reason for it.

2) The call to AfxBeginThread creates the thread in a suspended state. Without this, you have a race condition between the assignment to thread->m_bAutoDelete and the end of the thread function. That is, if the thread function completes execution before you access thread->m_bAutoDelete, you will get an access violation.

3) AfxBeginThread dynamically allocates the CWinThread object, so you need to delete it. I used an auto_ptr for this, but you could instead call delete thread at the end of the function. The advantage of the auto_ptr is that it will clean up even if an exception occurs within the function.

Let me know if this works for you and/or if you have additional questions.
0 Kudos
Message 2 of 2
(3,560 Views)