在 MFC 中同时挂起 2 个线程的问题!

Posted

技术标签:

【中文标题】在 MFC 中同时挂起 2 个线程的问题!【英文标题】:Problem in suspending 2 threads at the same time in MFC! 【发布时间】:2010-04-29 11:07:42 【问题描述】:

我正在学习线程和多线程..所以我刚刚创建了一个小应用程序,我将在其中更新 进度条和使用线程的静态文本。我从用户那里得到两个输入,开始值和结束值 循环应该旋转多长时间。我的应用程序中有 2 个线程。

Thread1-更新进度条(根据循环)将显示计数(循环计数)的静态文本。 Thread2 - 更新另一个仅显示名称的静态文本

基本上,如果用户单击开始,进度条会增加,同时文件计数和名称会并行显示。 还有另一个操作,如果用户点击暂停它(线程)必须暂停,直到用户点击恢复。 问题是,以上对于两个线程都不起作用(不会暂停和恢复)..但适用于单线程。 请检查代码以获得想法并回复我可以做什么!

点击按钮开始

void CThreadingEx3Dlg::OnBnClickedStart()

    m_ProgressBar.SetRange(start,end);
    myThread1 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction1,this);
    myThread2 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction2,this);

线程1

UINT MyThreadFunction1(LPARAM lparam)

    CThreadingEx3Dlg* pthis = (CThreadingEx3Dlg*)lparam;
    for(int intvalue =pthis->start;intvalue<=pthis->end; ++intvalue)
    
        pthis->SendMessage(WM_MY_THREAD_MESSAGE1,intvalue);
    
    return 0;

线程1函数

LRESULT CThreadingEx3Dlg::OnThreadMessage1(WPARAM wparam,LPARAM lparam)

    int nProgress= (int)wparam;
     m_ProgressBar.SetPos(nProgress);
       CString strStatus;
    strStatus.Format(L"Thread1:Processing item: %d", nProgress);
        m_Static.SetWindowText(strStatus);
    Sleep(100);
    return 0;

线程2

UINT MyThreadFunction2(LPARAM  lparam)

    CThreadingEx3Dlg* pthis = (CThreadingEx3Dlg*)lparam;
    for(int i =pthis->start;i<=pthis->end;i++)
    
        pthis->SendMessage(WM_MY_THREAD_MESSAGE2,i);
    
    return 0;

线程2函数

LRESULT CThreadingEx3Dlg::OnThreadMessage2(WPARAM wparam,LPARAM lparam)

        m_Static1.GetDlgItem(IDC_STATIC6);
        m_Static1.SetWindowTextW(L"Thread2 Running");
        Sleep(100);
        m_Static1.SetWindowTextW(L"");
        Sleep(100);
        return TRUE;



void CThreadingEx3Dlg::OnBnClickedPause()

    // TODO: Add your control notification handler code here
    if(!m_Track)
    
        m_Track = TRUE;
        GetDlgItem(IDCANCEL)->SetWindowTextW(L"Resume");
        myThread1->SuspendThread();
        WaitForSingleObject(myThread1->m_hThread,INFINITE);
        myThread2->SuspendThread();
        m_Static.SetWindowTextW(L"Paused..");

    
    else
    
        m_Track = FALSE;
        GetDlgItem(IDCANCEL)->SetWindowTextW(L"Pause");
        myThread1->ResumeThread();
        myThread2->ResumeThread();

        /*myEventHandler.SetEvent();
        WaitForSingleObject(myThread1->m_hThread,INFINITE);*/
    

【问题讨论】:

您说问题在于线程没有正确挂起/恢复,但您没有向我们展示处理“暂停”和“恢复”按钮点击的代码。 除此之外,我知道您这样做是为了学习线程,但这似乎是一项更适合计时器的任务。 另外,你说它“不起作用”。它以什么方式不起作用?它会崩溃吗?停止响应?就继续更新显示? 它只是冻结了无限时间!说没有响应 【参考方案1】:

我想我应该把cmets中的一些讨论总结成一个答案。

在 Windows 编程中,您永远不应该尝试从后台线程操作 GUI 控件,因为这样做会导致您的程序死锁。这意味着只有主线程应该接触 GUI 的元素。 (从技术上讲,重要的是哪个线程创建了控件,但在后台线程中创建控件并不常见)。

Joe Newcomer's article on worker threads 中详细说明了此要求(请参阅“工作线程和 GUI II:不要触摸 GUI”)。

您在线程过程中使用SendMessage。这会导致调用目标控件的适当消息处理程序,但在调用SendMessage的线程中。在您的情况下,这意味着后台线程运行消息处理程序,因此更新进度条和标签。

替代方法是使用PostMessage。这会导致消息被添加到队列中以由主线程的消息循环处理。当主线程开始运行时,它会按照消息添加到队列中的顺序处理消息,并调用消息处理程序本身。由于主线程拥有窗口,因此更新控件是安全的。

您还应该注意,SuspendThreadResumeThread 很难正确处理。您可能想阅读 Joe Newcomer 的文章 this section,其中描述了一些危险。


使用timer 通常可以更好地完成此类任务。这是一种让操作系统在特定时间过去后通知您的程序的机制。您可以使用如下计时器来实现它:

BEGIN_MESSAGE_MAP(CThreadingEx3Dlg, CDialog)
   ON_WM_DESTROY()
   ON_WM_TIMER()
END_MESSAGE_MAP()

void CThreadingEx3Dlg::OnTimer(UINT_PTR nTimerID)

   static int progress = 0;
   if (nTimerID == 1)
   
      m_ProgressBar.SetPos(progress);
      CString strStatus;
      strStatus.Format(_T("Processing item: %d"), progress);
      m_Static.SetWindowText(strStatus);
      progress++;
      if (progress > end) // If we've reached the end of the updates.
         KillTimer(1);
   


BOOL CThreadingEx3Dlg::OnInitDialog()

   // ... initialize controls, etc, as necessary.
   SetTimer(1, 100, 0);


void CThreadingEx3Dlg::OnDestroy()

   KillTimer(1);

如果您希望同时处理两个更新,它们可以使用相同的计时器。如果它们需要在不同的时间发生(例如一个以 100 ms 的间隔发生,另一个以 150 ms 的间隔发生),那么您可以使用不同的 ID 调用两次 SetTimer。要暂停操作,请致电KillTimer。要恢复它,请再次致电SetTimer

【讨论】:

TBH 如果您知道自己在做什么,那么从不同的线程进行更新是没有危险的。我一直这样做,没有问题:)【参考方案2】:

多线程和消息队列是一个相当复杂的游戏。当您从 ThreadA SendMessage 到同一个线程时,它只会调用消息处理程序。如果您从 ThreadA 到另一个线程 (ThreadB) 执行此操作,那么它会变得更加复杂。然后,ThreadA 向 ThreadB 的消息队列发送一条消息,并等待一个信号表明 ThreadB 已完成对消息的处理并发送了返回值。这引发了一个即时问题。如果 ThreadB 没有发送消息,那么您将陷入死锁,因为 ThreadB 中的消息永远不会被“分派”。这也引发了一个更大的问题。如果 ThreadB 的消息需要将消息发送到在 ThreadA 中创建的控件,那么您将遇到巨大的体系结构问题。由于 ThreadA 当前处于挂起状态,等待 ThreadB 返回,而 ThreadB 处于挂起状态,等待 ThreadA 返回。什么都不会发生......他们都将被暂停。

真的就是这样。只要您牢记这些问题,它就很容易。即,尽管其他人说了什么,这绝对是可能的。

一般来说,尽管您的线程非常没有意义,因为您会立即向主线程发送消息以进行一些处理。为什么要首先启动线程。您最好不要打扰,因为线程只会坐在那里等待主线程返回。

当你挂起第一个线程时,为什么你仍然“WaitForSingleObject”?为什么不暂停他们两个。

不过,您并没有提供足够的信息来说明您正在做什么,从而无法准确说明发生了什么。例如,当您单击暂停时会发生什么?

【讨论】:

【参考方案3】:

当多个线程与 GUI 交互时,Windows 将无法正常运行。您需要重新组织您的程序,以免发生这种情况。

【讨论】:

非常感谢您的回复..我现在有 2 个关于您的回答的问题 1.我们可以使用 2 个或更多线程来执行操作,例如读取文件或检查线程所在文件中的内容与GUI无关? 2.请告诉我另一种方法可以做到吗? @kiddo,是的,从后台线程操作非 GUI“事物”很好。另一种方法是使用PostMessage 而不是SendMessage。这会导致将消息添加到主线程的消息队列以在那里处理,而不是由调用线程立即处理——尽管正如我在上面的评论中指出的那样,使用计时器可能会更好地处理这样的事情。 @kiddo 如果您使用PostMessage,您还需要将Sleep 调用移出消息处理函数并进入线程函数,以免使主线程进入睡眠状态: ) 好的,我不确定如何使用定时器。它就像在特定时间后使用 SetTimer 调用线程一样,你能给我举个例子吗 我还尝试用 postMessage 替换 SendMessage ..事情是,一个线程将一次运行..我猜这不是实际的线程

以上是关于在 MFC 中同时挂起 2 个线程的问题!的主要内容,如果未能解决你的问题,请参考以下文章

某些数据处理时 MFC 应用程序 gui 挂起

请在 MFC 中发布任何线程示例?

在 MFC 中同时显示消息和进度时出现问题?

zmq_ctx_destroy() 在 MFC dll 中挂起

MFC体系结构

MFC 应用程序在 CSingleLock Lock() 后挂起