5

工作线程与主 UI 线程通信的最佳方式是什么?

简介: 我的 C++/MFC 应用程序是基于对话框的。为了进行冗长的计算,主 UI 线程创建了几个工作线程。当工作线程在计算中进行时,它们会将其进度报告给主 UI 线程,然后显示进度。

这适用于共享内存中的数字进度值(由工作人员编写,由 UI 读取),但我在处理文本进度消息时遇到了问题。我尝试的解决方案经过了多次迭代,但似乎都没有奏效。

  1. 我让 UI 线程将指向控件的指针传递给工作人员,工作人员直接更新了 UI。这不是很有效,而且似乎是错误的方法。

  2. 我让工作人员使用 SendMessage 向 UI 线程的窗口发送消息。这就陷入了僵局。(在消息被处理之前,SendMessage 不会返回。)

  3. 与 (2) 相同,除了使用 PostMessage 到 UI 线程的窗口。这工作了一段时间,然后消息丢失了。(PostMessage 立即返回。)进一步调查显示消息队列的配额(默认为 10,000)已被超出。

  4. 我增加了消息队列的配额(注册表中的变量 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERPostMessageLimit),但丢失消息的数量没有改变。

  5. 我让每个工作线程在 4 KB 缓冲区中缓冲消息,并在缓冲区填满时使用 PostMessage。这失败了,因为 UI 线程从未收到任何消息。当我将缓冲区大小增加到 64 KB 时也是如此。

工作线程以“最低”优先级运行,UI 线程以“正常”优先级运行。工作线程正在发送带有类似代码的消息

UIMessage *pUI=new UIMessage; // so it won't go out of scope (main dialog will delete it)
pUI->buffer=traceLineBuffer; pUI->nOutputs=traceN;
BOOL ok=::PostMessage(hWndMainDlg,TraceLineMsg,(WPARAM)pUI, NULL/*lParam*/);

并且 UI 正在使用类似的代码接收它们

BEGIN_MESSAGE_MAP(CMainDlg, CDialog)
...
ON_MESSAGE(TraceLineMsg,OnTraceLineMsg)
...
END_MESSAGE_MAP()

LRESULT CMainDlg::OnTraceLineMsg(WPARAM wParam, LPARAM lParam)
{
    UIMessage *pUI=(UIMessage *)wParam;
    char *p=pUI->buffer;
    // PROCESS BUFFER
    delete[] pUI->buffer;
    delete pUI;
    return 0;
}

问题:

  1. 在可能会出现数千个文本报告的情况下,工人发布进度报告的首选方式是什么?

  2. 为什么不能增加队列中发帖的配额?

  3. 为什么主 UI 线程似乎永远不会收到缓冲区中的消息,即使传输它们的机制与发布单个报告相同?

64 位 Windows 7、Visual Studio 2010、本机 C++/MFC

4

3 回答 3

4

在 WaitForMultipleObjects 调用中的主线程不会处理任何消息,也不会更新控件或其他窗口。解决方案是:不要那样做。

于 2013-10-03T00:05:56.357 回答
1

在 GUI 上发布进度报告的速度要快于用户可以吸收的速度并没有多大意义。当其他线程中有大量活动时,通常使用 GUI 计时器轮询线程中的进度变量,因此每隔 500 毫秒更新一次 GUI 控件。

这是定时器轮询实际上具有优势的极少数情况之一。您可以获得定期进度报告,而无需在 GUI Windows 消息队列中塞满更新。例如,uTorrent 客户端(存在大量网络活动)使用此方案 - 尝试在收到的每个网络协议单元上更新 GUI 下载统计信息肯定会填满 GUI。

您在 (5) 中的缓冲方案应该有效。我经常通过将对象指针加载到 LPARAM 或 WPARAM 中来将大型数据项传输到主 GUI 线程,在工作线程中对它们进行新的处理,并在显示后在 GUI 中将它们删除。您的 (5) 应该有效,并且至少减少了进度数据传输的开销。我只能假设要显示的数据量仍然太大,所以 GUI 线程仍然跟不上:(

于 2013-10-03T10:39:34.157 回答
1

Windows 上的 MFC 工作线程有多种与主线程通信的选项。您拥有标准的线程信号和同步原语(互斥量、信号量、事件)、易于使用的PostMessage以及更高性能的I/O 完成端口机制。

// syncronization
{
    CSingleLock lock(&sharedCriticalSection,TRUE);
    sharedList.push_back(msg);
}
// other thread(s) are blocked/pending or you send an event or message to signal

// messages
Data* data = new Data(payload);
PostMessage(hWnd, REGISTERED_MESSAGE, 0, (LPARAM)data);
// target window handles message and deletes data 
// if it is not blocked or too slow and the queue overflows

// skipping lots of IO completion port boilerplate and showing the key methods
messagePort = CreateIoCompletionPort(...);
...
GetQueuedCompletionStatus(messagePort,...);
...
PostQueuedCompletionStatus(messagePort,...);

如果您阻塞或忙于等待线程完成,它们都不会完成太多工作或提高您的性能或响应能力。

对您的观察的评论:

  1. 不要让工人触摸 GUI。
  2. 不要使用工作线程中的 SendMessage。
  3. PostMessage 适用于低容量,只要 UI 可以跟上并且没有被阻止。
  4. 如果你认为这需要改变,你应该重新考虑你的解决方案。发送更少或使用更高性能的选项。
  5. 如果 UI 未被阻止,合并消息可能会有所帮助。

回答您的问题:

  1. 重新评估您是否真的需要一次性发送数千条消息。用户每秒需要多少更改?如果您需要全部发送,请查看 I/O 完成端口机制。
  2. 我不会尝试,它可能会奏效,除非......
  3. 您的主 UI 线程被阻止等待工作人员在 WaitForMultipleObjects 中完成,并且您的工作人员和其他事件在被阻止时必须生成超过最大队列消息。
于 2016-08-31T19:52:37.173 回答