4

I've just started with learning MFC and I'm writing one dialog based application for better understanding of Multi-Threading.

The main dialog has a progress bar, a Start button and a Cancel button.

On click of the start button, i'm creating a worker thread to do some processing(through API call) and the main thread takes care of Progress bar.

I've defined a couple of Windows Messages to update and stop Progress bar status

WM_UPDATE_CONTROL
WM_STOP_CONTROL

Below is the code that i've created so far

HWND* phObjectHandle;
CWinThread* thread;

void CprogCtrlDlg::OnBnClickedStart() {
    phObjectHandle = new HWND;    // Set object handle for Worker thread
    *phObjectHandle = GetSafeHwnd();

    // create worker thread
    if(NULL == (thread = AfxBeginThread(ThreadFunc, phObjectHandle))) {
        EndDialog(IDCANCEL);
    }

    AfxMessageBox(L"Thread started");
    // Set Progress bar to marquee
}

void CprogCtrlDlg::OnBnClickedCancel() {
    // kill the Worker thread
}

UINT CprogCtrlDlg::ThreadFunc(LPVOID pParam) { 
    HWND *pObjectHandle = static_cast<HWND *>(pParam);
    CprogCtrlImpDlg* threadDlg = (CprogCtrlImpDlg*) pParam;

    return threadDlg->ThreadFuncRun(pObjectHandle);
}

UINT CprogCtrlDlg::ThreadFuncRun(HWND* pObjectHandle) {

    ::PostMessage(*pObjectHandle, WM_UPDATE_CONTROL, 0, 0);

    // repetitive API CALL in a loop

    ::PostMessage(*pObjectHandle, WM_STOP_CONTROL, 0, 0);
    AfxMessageBox(L"Thread completed");

    return 0;
}

I want to terminate the Worker thread from a Parent thread, if a Cancel button is clicked.

I tried using TerminateThread()(though it wasn't a suggested one) but I couldn't kill the thread.

Please comment and share your thoughts on terminating a worker thread from a parent thread.

I'm using visual studio 2010 on Windows 7

TIA

5
  • 1
    Just a quick point, never ever use TerminateThread. Jan 14, 2014 at 15:01
  • @Roger Yes(a bad idea to use it)!!!. I just tried it to check if it can solve the problem.. Jan 14, 2014 at 15:03
  • Also, no need to do delete pObjectHandle you'll probably crash. Also you cast your lParam to two different types, which won't work. The correct way to communicate between threads is with events. There are many examples on SO. I'm afraid there are lots of problems with your current code :-( Jan 14, 2014 at 15:04
  • I tried using WaitForSingleObject, but that wasn't working just fine. It tries and executes the first API call successfully and then tries to terminate it Jan 14, 2014 at 15:07
  • 1
    I've updated my answer with some examples based on your current code - there are many ways to do this, but perhaps this is a useful start. Jan 14, 2014 at 15:38

1 Answer 1

6

I would amend your code something like this.

Have some member variables in your dialog class to hold the thread handle and an event handle (initialise to NULL in the constructor):

CWinThread* m_hThread;
HANDLE m_hKillEvent;

Use a static function as your thread entry point, pass the dialog this as the parameter, then delegate the call back to the class instance so you have access to all of the dialog's variables:

UINT ThreadFunc(LPVOID pParam) 
{ 
    // static thread func - delegate to instance
    CprogCtrlDlg* pDlg = static_cast<CprogCtrlDlg*>(pParam);
    return pDlg->ThreadFuncRun();
}

When you start the thread, create an event too:

void CprogCtrlDlg::OnBnClickedStart() 
{
    // create worker thread
    m_hKillEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    m_hThread = AfxBeginThread(ThreadFunc, this);
    AfxMessageBox(L"Thread started");
}

To kill the thread, just set the event and wait on the thread handle, which will get signaled on death:

void CprogCtrlDlg::OnBnClickedCancel() 
{
    // kill the Worker thread
    SetEvent(m_hKillEvent);

    // wait for it to die
    DWORD dwRet = WaitForSingleObject(m_hThread->m_hThread, 5000);
    if (dwRet == WAIT_TIMEOUT)
    {
        // thread failed to die after 5 seconds
        // error handling (maybe TerminateThread here)
    }
}

In the thread function (now in the dialog class) you can post messages to yourself to indicate progress and use a wait on the event to catch a kill request:

UINT CprogCtrlDlg::ThreadFuncRun() 
{
    // instance thread func
    PostMessage(WM_UPDATE_CONTROL, 0, 0);

    // main loop
    while (true)
    {
        // check kill
        DWORD dwRet = WaitForSingleObject(m_hKillEvent, 0);
        if (dwRet == WAIT_OBJECT_0) break;

        // do a little work here and update progress
        // ... so this is part of your working loop ...
        PostMessage(WM_UPDATE_CONTROL, 0, 1 /*2,3,4,...*/);
    }

    // normal thread exit
    PostMessage(WM_STOP_CONTROL, 0, 0);
    return 0;
}

I've left out initialisation, cleanup of pointers, handles etc. but you get the general idea I hope.

There are several ways you can code the thread loop, you can do it like above where you periodically check to see if the event is signaled, or you can wait on the event to get signaled to do the work. Both are common patterns and often used together with two events - one for triggering work and the other for killing. See this answer for some important points to note if waiting on multiple events.

For a simple progress bar update, you can put the event check inside the work loop, something like this:

UINT CprogCtrlDlg::ThreadFuncRun() 
{
    // instance thread func
    PostMessage(WM_UPDATE_CONTROL, 0, 0);

    // main loop
    for (int i = 0; i < 100; ++i)
    {
        // check kill
        DWORD dwRet = WaitForSingleObject(m_hKillEvent, 0);
        if (dwRet == WAIT_OBJECT_0) break;

        // do a little work here and update progress
        PostMessage(WM_UPDATE_CONTROL, 0, (LPARAM)i);
    }

    // normal thread exit
    PostMessage(WM_STOP_CONTROL, 0, 0);
    return 0;
}
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.