从父线程终止一个工作线程 - MFC

4

我刚开始学习MFC,并正在编写一个基于对话框的应用程序,以更好地理解多线程。

主对话框包括进度条、启动按钮和取消按钮。

点击启动按钮后,我创建了一个工作线程来进行一些处理(通过API调用),而主线程负责处理进度条。

我定义了几个Windows消息来更新和停止进度条状态。

WM_UPDATE_CONTROL
WM_STOP_CONTROL

以下是我目前创建的代码:
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;
}

如果单击取消按钮,我希望能够从父线程中终止工作线程。

我尝试使用TerminateThread()(虽然这不是建议的方法),但我无法杀死线程。

请评论并分享您在从父线程终止工作线程方面的想法。

我正在Windows 7上使用Visual Studio 2010。

谢谢提前。


1
只是一个快速的提示,永远不要使用 TerminateThread - Roger Rowland
@Roger 是的(使用它是个坏主意)!!!我只是尝试了一下,看看它能否解决问题。 - MajorBlackViking
另外,不需要执行 delete pObjectHandle,否则你可能会崩溃。此外,你将 lParam 强制转换为两种不同的类型,这是行不通的。在线程之间进行通信的正确方法是使用事件。在 SO 上有许多示例。恐怕你当前的代码存在很多问题 :-( - Roger Rowland
我尝试使用WaitForSingleObject,但它并没有完美地工作。它尝试并成功执行第一个API调用,然后尝试终止它。 - MajorBlackViking
1
我已经根据您当前的代码更新了我的答案,并提供了一些示例 - 有很多方法可以做到这一点,但也许这是一个有用的开始。 - Roger Rowland
1个回答

6
我会像这样修改您的代码。
在对话框类中添加一些成员变量来保存线程句柄和事件句柄(在构造函数中初始化为NULL):
CWinThread* m_hThread;
HANDLE m_hKillEvent;

使用静态函数作为您的线程入口点,将对话框this作为参数传递,然后将回调委派给类实例,以便您可以访问所有对话框的变量:

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

当你启动线程时,也要创建一个事件:

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

要终止线程,只需设置事件并等待线程句柄,该句柄将在线程结束时发出信号:

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)
    }
}

在线程函数中(现在在对话框类中),您可以向自己发送消息以指示进度,并使用事件等待来捕获停止请求:

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;
}

我已省略了初始化、指针、句柄等清理工作,但希望您能理解一般的想法。
有几种方法可以编写线程循环,您可以像上面那样定期检查事件是否被触发,也可以等待事件被触发来完成工作。这两者都是常见的模式,并且经常与两个事件一起使用——一个用于触发工作,另一个用于终止工作。如果要在等待多个事件时注意一些重要点,请参见此答案
对于简单的进度条更新,您可以将事件检查放在工作循环内部,类似于以下内容:
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接