【发布时间】:2019-01-24 08:31:00
【问题描述】:
我的目标是编写一个类(我们称它为CProgressDlg),当主 UI 线程中的某些操作需要超过 1 秒才能完成时,该类可用于显示带有进度条的对话框窗口。所以之前写的一个方法:
if(do_work)
{
for(int i = 0; i < a_lot; i++)
{
//Do work...
::Sleep(100); //Use sleep to simulate work
}
}
可以像这样轻松调整(伪代码):
if(do_work)
{
CProgressDlg m_progDlg;
for(int i = 0; i < a_lot; i++)
{
//Do work...
::Sleep(100); //Use sleep to simulate work
if(m_progDlg.UpdateWithProgress(i))
{
//User canceled it
break;
}
}
}
所以要实现它,我将从CProgressDlg 构造函数启动一个工作线程:
::CreateThread(0, 0, ThreadProcProgressDlg, (LPVOID)0, 0, 0);
然后我会从一个工作线程创建一个无模式对话框,为用户显示进度条和一个取消按钮:
DWORD WINAPI ThreadProcProgressDlg(
_In_ LPVOID lpParameter
)
{
//Wait a little
::Sleep(1000);
HMODULE hModule = AfxGetResourceHandle();
ASSERT(hModule);
//Get parent window
//(Can't use main window, as its UI thread is blocked)
HWND hParentWnd = NULL;
const static BYTE dlgTemplate[224] = {
0x1, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc8, 0x0, 0xc8, 0x90, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0xdb, 0x0, 0x4b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x90, 0x1, 0x0, 0x1, 0x4d, 0x0, 0x53, 0x0, 0x20, 0x0, 0x53, 0x0, 0x68, 0x0, 0x65, 0x0, 0x6c, 0x0, 0x6c, 0x0, 0x20, 0x0, 0x44, 0x0, 0x6c, 0x0, 0x67, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x50, 0x92, 0x0, 0x36, 0x0, 0x42, 0x0, 0xe, 0x0, 0x2, 0x0, 0x0, 0x0, 0xff, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x81, 0x0, 0x2, 0x50, 0x7, 0x0, 0x7, 0x0, 0xcd, 0x0, 0x19, 0x0, 0xed, 0x3, 0x0, 0x0, 0xff, 0xff, 0x82, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x50, 0x7, 0x0, 0x21, 0x0, 0xcd, 0x0, 0x7, 0x0, 0xec, 0x3, 0x0, 0x0, 0x6d, 0x0, 0x73, 0x0, 0x63, 0x0, 0x74, 0x0, 0x6c, 0x0, 0x73, 0x0, 0x5f, 0x0, 0x70, 0x0, 0x72, 0x0, 0x6f, 0x0, 0x67, 0x0, 0x72, 0x0, 0x65, 0x0, 0x73, 0x0, 0x73, 0x0, 0x33, 0x0, 0x32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x81, 0x0, 0x2, 0x50, 0x7, 0x0, 0x29, 0x0, 0xcd, 0x0, 0x8, 0x0, 0xee, 0x3, 0x0, 0x0, 0xff, 0xff, 0x82, 0x0, 0x0, 0x0, 0x0, 0x0, };
//Show dialog
HWND hDlgWnd = ::CreateDialogIndirectParam(hModule, (LPCDLGTEMPLATE)dlgTemplate, hParentWnd, DlgWndProc, (LPARAM)0);
ASSERT(hDlgWnd);
if(hDlgWnd)
{
::ShowWindow(hDlgWnd, SW_SHOW);
}
return 0;
}
最小的对话过程(只是为了显示它)将是这样的:
INT_PTR CALLBACK DlgWndProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(wParam);
UNREFERENCED_PARAMETER(lParam);
switch (uMsg)
{
case WM_INITDIALOG:
{
}
return TRUE;
case WM_COMMAND:
{
UINT uCmd = LOWORD(wParam);
if (uCmd == IDOK ||
uCmd == IDCANCEL)
{
::DestroyWindow(hDlg);
return (INT_PTR)TRUE;
}
}
break;
}
return (INT_PTR)FALSE;
}
但是当我运行这段代码时,我的无模式对话框会显示片刻然后消失。我知道我可能没有做任何事情来从工作线程中正确显示它。
知道我错过了什么吗?
【问题讨论】:
-
为什么要把繁重的工作交给GUI线程,把进度条交给工作线程?不应该反过来吗?
-
@Alex:我明白这一点。就像我说的,这是已经编写的代码的补丁。
-
示例:用户单击列表列对其进行排序(包含很多项目的列表)。在当前代码中,它可能会使用旋转的鼠标光标阻塞主线程约 5 秒。我想写这门课来解决这样的情况。 (这只是我想到的一个例子。)
-
也许有一个错误导致destroy函数被无意调用?通过调试断点单步执行代码会留下任何线索吗?
-
既然你在主线程中做繁重的工作,那么仅仅为了更新GUI而创建一个工作线程是没有意义的。它肯定会产生比解决更多的问题。也就是说,如果您需要从工作线程更新部分
GUI,您通常会使用PostMessage发布消息,并让主线程在繁重的工作循环中处理此消息。同样,这并不比让主线程完成所有工作更好(事实上,它更糟)。
标签: c++ multithreading winapi mfc dialog