C++: MFC and threading problem

Started by
9 comments, last by Antheus 15 years, 3 months ago
My skills at using MFC (MSVC 2008) and threading are basic at best, so combine the two and what do I get? A problem! Here's my setup. I make a new MFC dialog-based application. I add a List Box to the dialog and make it a control vairbale, which gives me a CListBox member variable through which I can control the List Box. In the ::OnInitDialog() method, I 'new' an object of my own making which I've called 'ThreadObject'. This class spawns a new thread during construction and kills the thread during destruction. I also pass to ThreadObject a pointer to the CListBox member during construction. Only the ThreadObject uses the List Box, so (I would think) threading shouldn't be an issue with it (but I think this is the root of my problem, somehow). Everything appears to work fine at first. In the spawned thread of the ThreadObject object, I use the List Box to print messages. No problem. However, when I go to delete the ThreadObject (through a button press for now) and stop the thread, everything hangs and I can't figure out why. Here's the source:
// CTestDlg.h
//...
public:
    CListBox m_ListBox;
    ThreadObject* m_pThreadObject;
    afx_msg void OnBnClickedKillThread();
//...


// CTestDlg.cpp
BOOL CTestDlg::OnInitDialog()
{
   //...

   m_pThreadObject = new ThreadObject(&m_ListBox);
}
void CTestDlg::OnBnClickedKillThread()
{
    delete m_pThreadObject;
}
// ThreadObject.h
#ifndef INC_THREAD_OBJECT_H
#define INC_THREAD_OBJECT_H

class ThreadObject
{
public:
    ThreadObject(CListBox* pListBox);
    ~ThreadObject();
    void ThreadObjectThread();

private:
    HANDLE          m_Thread;
    DWORD           m_ThreadID;
    volatile bool   m_bThreadRun;
    CListBox*       m_pListBox;
};

#endif // INC_THREAD_OBJECT_H
// ThreadObject.cpp
#include "stdafx.h"
#include "ThreadObject.h"

DWORD WINAPI ThreadProc(void* param);

// Constructor
ThreadObject::ThreadObject(CListBox* pListBox)
    : m_bThreadRun(true), 
      m_Thread(NULL), 
      m_pListBox(pListBox)
{
    m_Thread = CreateThread(NULL, 0, ThreadProc, this, 0, &m_ThreadID);
}
// Destructor
ThreadObject::~ThreadObject()
{
    m_bThreadRun = false;

    WaitForSingleObject(m_Thread, INFINITE);
    CloseHandle(m_Thread);
}

// WINAPI thread entry point
DWORD WINAPI ThreadProc(void* param)
{
    ThreadObject* pThreadObject = reinterpret_cast<ThreadObject*>(param);
    pThreadObject->ThreadObjectThread();
    return 0;
}
// ThreadObject thread entry point
void ThreadObject::ThreadObjectThread()
{
    while (m_bThreadRun)
    {
        Sleep(1000);
        m_pListBox->InsertString(0, L"Tick");
    }
}
When I start this up and run, "Tick" gets inserted to the list box about every 1 second as expected. No problem. But when I click the Kill Thread button which deletes the pointer to the ThreadObject and ~ThreadObject gets called, bad things happen. m_bThreadRun is set to false and the thread seems to exit just fine, but I never return from the WaitForSingleObject() call and I don't know why.
Advertisement
Quote:Original post by Mantear
My skills at using MFC (MSVC 2008) and threading are basic at best, so combine the two and what do I get? A problem!

Here's my setup. I make a new MFC dialog-based application. I add a List Box to the dialog and make it a control vairbale, which gives me a CListBox member variable through which I can control the List Box. In the ::OnInitDialog() method, I 'new' an object of my own making which I've called 'ThreadObject'. This class spawns a new thread during construction and kills the thread during destruction. I also pass to ThreadObject a pointer to the CListBox member during construction. Only the ThreadObject uses the List Box, so (I would think) threading shouldn't be an issue with it (but I think this is the root of my problem, somehow).

Everything appears to work fine at first. In the spawned thread of the ThreadObject object, I use the List Box to print messages. No problem. However, when I go to delete the ThreadObject (through a button press for now) and stop the thread, everything hangs and I can't figure out why. Here's the source:

*** Source Snippet Removed ***

*** Source Snippet Removed ***

*** Source Snippet Removed ***

When I start this up and run, "Tick" gets inserted to the list box about every 1 second as expected. No problem. But when I click the Kill Thread button which deletes the pointer to the ThreadObject and ~ThreadObject gets called, bad things happen.

m_bThreadRun is set to false and the thread seems to exit just fine, but I never return from the WaitForSingleObject() call and I don't know why.
What if you take MFC out of the picture and never have the thread do anything but Sleep(1000)? Does WaitForSingleObject() return then? Are you verifying this with breakpoints and the debugger?
Quote:Original post by Evil Steve
What if you take MFC out of the picture and never have the thread do anything but Sleep(1000)? Does WaitForSingleObject() return then? Are you verifying this with breakpoints and the debugger?


If I do nothing but comment out the call to InsertString() using pListBox from the ThreadObject thread, WaitForSingleObject() returns with no problems. The output window shows "The thread 'Win32 Thread' (0xc24) has exited with code 0 (0x0).", verifying that the thread has stopped. If I keep the call to InsertString() in, the thread never exits because it never returns from WaitForSingleObject().
Quote:Original post by Mantear
Quote:Original post by Evil Steve
What if you take MFC out of the picture and never have the thread do anything but Sleep(1000)? Does WaitForSingleObject() return then? Are you verifying this with breakpoints and the debugger?


If I do nothing but comment out the call to InsertString() using pListBox from the ThreadObject thread, WaitForSingleObject() returns with no problems. The output window shows "The thread 'Win32 Thread' (0xc24) has exited with code 0 (0x0).", verifying that the thread has stopped. If I keep the call to InsertString() in, the thread never exits because it never returns from WaitForSingleObject().
Ok, how about using this instead:
SendMessage(m_ListBox, LB_ADDSTRING, 0, (LPARAM)L"Tick");
That does it without touching MFC (Apart from getting the HWND from the CListBox).

That's about as far as my MFC knowledge goes unfortunately...
Quote:Original post by Evil Steve
Ok, how about using this instead:
SendMessage(m_ListBox, LB_ADDSTRING, 0, (LPARAM)L"Tick");
That does it without touching MFC (Apart from getting the HWND from the CListBox).

That's about as far as my MFC knowledge goes unfortunately...


Using that method, the "Tick" messages still get to the List Box, but the thread still hangs. :(
Quote:A thread that uses a wait function with no time-out interval may cause the system to become deadlocked. Two examples of code that indirectly creates windows are DDE and the CoInitialize function. Therefore, if you have a thread that creates windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForSingleObject.


WaitForSingleObject.
When it's frozen, in the debugger select break all from the debug menu. Also turn on the threads window (Debug->Windows->Threads) so you can switch between threads easily.

From there you should be able to see what each thread is up to in the debugger and step through them if they are still running, which should help you find out why the thread isn't stopping.

However in this case reading the documentation for SendMessage() might be a good plan. Most notably SendMessage blocks until the receiving thread has processed the message, however in your case the receiving thread will be waiting for the sending thread to exit, and therefore won't be processing messages. That means you'll be deadlocked.

The way round that is to find another way of getting the message to the main thread. PostMessage() for example.
Antheus's quote/link on the danger of WaitForSingleObject() describes the problem, I believe. I'm deleteing the ThreadObject from the main UI thread. This causes the main UI thread to Wait on the completion of my created thread. But the main UI thread needs to handle all messages coming to the window, which it can't do if it's waiting for WaitForSingleObject to return, which seems to cause the deadlock. Somewhere in the process of WaitForSingleObject(), the window (UI thread) must need to access some message(s) for it to work.

Now, the big question is, how do I get around this? The link recommends using MsgWaitForMultipleObjects<Ex>, but I'm not sure how that works. I've read the documentation and played with it a bit, but I'm still missing something. It's either not waiting for the thread to finish or still dead-locks.
The probable cause is an optimization the compiler does. Since you don't access m_bThreadRun in your thread function the compiler optimizes the check away.

Try marking m_bThreadRun with volatile.

AFAIK there's no deadlock if a SendMessage call calls another SendMessage inside the WindowProc.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Quote:Original post by Mantear
Now, the big question is, how do I get around this? The link recommends using MsgWaitForMultipleObjects<Ex>, but I'm not sure how that works. I've read the documentation and played with it a bit, but I'm still missing something. It's either not waiting for the thread to finish or still dead-locks.
From what I understand, it's because adding to the list box causes the SendMessage() call. Messages are always processed by the thread that owns it, so it tries to get the main thread to process the message - but it's stalled in WaitForSingleObject().
I bet that if you move the Sleep() to after InsertString(), the bug will go away 99% of the time, since it'll be very unlikely you'll change the bool and enter WaitForSingleObject() between the while() condition and the InsertString() call. Note that this isn't a fix, it just shows if this is the likely cause (And using this as a fix will come back to haunt you later on).

Quote:Original post by Endurion
The probable cause is an optimization the compiler does. Since you don't access m_bThreadRun in your thread function the compiler optimizes the check away.

Try marking m_bThreadRun with volatile.

AFAIK there's no deadlock if a SendMessage call calls another SendMessage inside the WindowProc.
m_bThreadRun is already volatile.

This topic is closed to new replies.

Advertisement