0

我正在 MFC 中编写一个带有后台工作线程(通过创建_beginthreadex)和 UI 线程的应用程序。从 UI 线程单击一个按钮以开始和结束工作线程。如果m_threadRunning标志为假,则启动后台线程,如果为真,则停止后台线程。我停止线程的方法是将m_threadRunning标志设置为 false 并调用WaitForSingleObject以让后台线程完成它正在做的事情。

我的应用程序有四种不同的状态。我让前三个状态正常工作,添加第四个状态是导致我的问题的原因。对于第四种状态,我希望能够对桌面进行采样并将平均 RGB 值发送到 COM 端口进行处理。当处于前三种状态中的任何一种时,如果我想停止向 COM 端口发送数据的执行,它将正常终止并且没有问题。如果我处于第四个状态并单击“停止”,应用程序将挂起,因为我没有时间来调用WaitForSingleObject.

我还有一个显示当前 RGB 值的自定义CEdit框。CColorEdit当我处于状态 3 或 4 时,我从后台线程更新它(因为它们都动态改变颜色)。当我设置调用Invalidate或的颜色时,我已将问题缩小到调用RedrawWindow

我提出了一些解决方案,但我不喜欢其中的任何一个,并且宁愿了解导致问题的原因,因为我在 MFC 中编写此问题的目标是学习和理解 MFC。以下是解决问题的方法:

  1. 我已经在我的工作线程中以大约 60 个样本/秒的速度调用 Sleep()。将其更改为较低的值,例如 30 个样本/秒,大部分时间都可以解决问题。
  2. 我在我的工作线程中轮询m_threadRunning以检查是否应该终止线程。如果我在对屏幕进行采样之后但在更新编辑控件之前对其进行轮询,这在大多数情况下都可以解决问题。
  3. 我在调用WaitForSingleObject和调用 TerminateThread 时超时 5 秒以在线程等待失败时手动终止线程,这始终解决了问题。这是我目前的解决方案。

以下是相关的代码位(我锁定了 outBytes 的任何使用):

void CLightControlDlg::UpdateOutputLabel()
{
    CSingleLock locker(&m_crit);
    locker.Lock();

    m_outLabel.SetColor(outBytes[1], outBytes[2], outBytes[3]); //the call to this freezes the program

    CString str;
    str.Format(L"R = %d; G = %d; B = %d;", outBytes[1], outBytes[2], outBytes[3]);
    m_outLabel.SetWindowText(str);
}

这部分代码用于终止工作线程

m_threadRunning = false;
locker.Unlock(); //release the lock...
//omitted re-enabling of some controls
//normally this is just WaitForSingleObject(m_threadHand, INFINITE);
if(WaitForSingleObject(m_threadHand, 5000) == WAIT_TIMEOUT) 
{
    MessageBox(L"There was an error cancelling the I/O operation to the COM port. Forcing a close.");
    TerminateThread(m_threadHand, 0);
}
CloseHandle(m_threadHand);
CloseHandle(m_comPort);
m_threadHand = INVALID_HANDLE_VALUE;
m_comPort = INVALID_HANDLE_VALUE;

我派生的编辑控件中更新文本颜色的代码:

void SetColor(byte r, byte g, byte b)
{
    _r = r;
    _g = g;
    _b = b;
    br.DeleteObject();
    br.CreateSolidBrush(RGB(r,g,b));
    Invalidate(); //RedrawWindow() freezes as well
}

最后,我的线程过程的代码:

unsigned int __stdcall SendToComProc(void * param)
{
    CLightControlDlg *dlg = (CLightControlDlg*)param;
    while(1)
    {
        if(!dlg->IsThreadRunning())
            break;

        switch(dlg->GetCurrentState())
        {
        case TransitionColor: //state 3
            dlg->DoTransition();
            dlg->UpdateOutputLabel();
            break;
        case ScreenColor: //state 4
            dlg->DoGetScreenAverages();
            //if(!dlg->IsThreadRunning()) break; //second poll to IsThreadRunning()
            dlg->UpdateOutputLabel();
            break;
        }
        dlg->SendToCom();
        Sleep(17); // Sleep for 1020 / 60 = 17 = ~60samples/sec
    }
    return 0;
}

非常感谢您提供的任何帮助!

4

1 回答 1

3

当工作线程尝试访问在主线程中创建的控件并且主线程在 WaitForSingleObject 中挂起时,您会遇到死锁。只有当主线程接受到控件的关联消息时,才能从工作线程更新控件。

从工作线程中删除对控件的所有访问。相反, PostMessage 将自定义消息发送到主线程中的窗口。这里有一个例子:

http://vcfaq.mvps.org/mfc/12.htm

可以使用相同的技术来通知主线程工作线程已完成,因此您可以避免 WaitForSingleObject。

于 2013-08-27T02:04:49.143 回答