G2EX

VC6多线程编程

一、问题的提出

编写一个耗时的单线程程序:在Visual C++ 6.0中新建一个基于对话框的MFC项目,这里取名「Demo」。

基于对话框的MFC项目

删掉主对话框IDD_DEMO_DIALOG上的控件,添加一个按钮和文本框。按钮的ID和标题设置为IDD_DEMO_DIALOG测试,文本框的ID设置为IDC_EDT_OUT

双击测试按钮为按钮添加响应函数OnBtnTest(),用于计算从1累加到100的和,为了看起来耗时,在每次循环中加入0.1秒的延时。

1
2
3
4
5
6
7
8
9
10
11
12
13
void CDemoDlg::OnBtnTest() 
{
GetDlgItem(IDC_EDT_OUT)->SetWindowText("计算开始...");
int sum = 0;
CString str;
for (int i = 1; i <= 100; ++i)
{
sum += i;
Sleep(100); // 延时0.1秒
}
str.Format("计算结果: %d", sum);
GetDlgItem(IDC_EDT_OUT)->SetWindowText(str);
}

编译并运行应用程序,单击测试按钮,会发现在这10秒多期间内程序卡死了,不再响应其它消息。为了更好地处理这种耗时的操作,我们有必要使用多线程编程。

程序卡死

二、如何使用多线程

下面在MFC中使用多线程完成1到100的累加计算。程序运行时,会自动创建一个Demo主线程,点击测试按钮后主线程启动一个计算线程执行ThreadFunc()函数,计算线程计算完成后发送WM_USER_THREAD_FINISHED消息,最后,主线程使用OnThreadFinished()函数处理WM_USER_THREAD_FINISHED消息并把结果显示在文本框中。

实现步骤如下:

1) 定义参数传递结构体

DemoDlg.h文件中CDemoDlg类的外部定义两个结构体,第一个用于主线程创建计算线程时向计算线程传递参数:

1
2
3
4
5
typedef struct tagTHREADPARMS {
int BeginNumb; // 起始数字
int EndNumb; // 结束数字
HWND hWnd;
} THREADPARMS;

当计算线程计算完成后向主线程发送消息时,使用下面的结构体:

1
2
3
4
typedef struct tagRESULTPARMS {
int Sum; // 计算结果
HWND hWnd;
} RESULTPARMS;

如果线程间不需要参数传递或者传递的参数很少,可以不使用结构体。

2) 声明线程函数

DemoDlg.h文件中CDemoDlg类的外部添加线程函数声明:

1
UINT ThreadFunc(LPVOID lpParam);

3) 编写线程函数

DemoDlg.cpp文件中编写计算线程函数。其计算结果是一个整形数字,完全可以用返回值的形式返回。为了应对更复杂的参数传递,这里演示用结构体传递参数,把结果以消息的形式发送给消息处理函数(消息处理函数后续添加)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UINT ThreadFunc(LPVOID lpParam)
{
// 获取传递过来的参数
THREADPARMS* ptp = (THREADPARMS*) lpParam;
HWND hWnd = ptp->hWnd;
int m = ptp->BeginNumb;
int n = ptp->EndNumb;
int sum = 0;

// 构造发送消息的参数
RESULTPARMS* prp = new RESULTPARMS;
prp->hWnd = hWnd;

for (int i = m; i <= n; ++i)
{
sum += i;
Sleep(100); // 延时0.1秒
}
prp->Sum = sum;

// 发送 WM_USER_THREAD_FINISHED 消息
::PostMessage (hWnd, WM_USER_THREAD_FINISHED, (WPARAM) prp, 0);

return 0;
}

4) 创建线程函数

在按钮事件OnBtnTest()中使用AfxBeginThread()创建线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
void CDemoDlg::OnBtnTest() 
{
// 构造启动线程的参数
THREADPARMS* ptp = new THREADPARMS;
ptp->hWnd = m_hWnd;
ptp->BeginNumb = 1;
ptp->EndNumb = 100;

// 启动计算线程
AfxBeginThread (ThreadFunc, ptp);

GetDlgItem(IDC_EDT_OUT)->SetWindowText("计算开始...");
}

5) 消息处理函数

计算线程发送结果的WM_USER_THREAD_FINISHED消息怎么来处理呢?

首先,在DemoDlg.h文件中定义WM_USER_THREAD_FINISHED消息:

1
#define WM_USER_THREAD_FINISHED WM_USER+0x100

然后,在DemoDlg.h文件CDemoDlg类的protected中添加消息函数的声明,把声明放到DECLARE_MESSAGE_MAP()行的上面。

1
afx_msg LONG OnThreadFinished(WPARAM wParam, LPARAM lParam);

这时就可以把WM_USER_THREAD_FINISHEDOnThreadFinished()对应起来了:在DemoDlg.cpp中,找到BEGIN_MESSAGE_MAP(CDemoDlg, CDialog),在END_MESSAGE_MAP()结束之前加入消息映射:

1
ON_MESSAGE (WM_USER_THREAD_FINISHED, OnThreadFinished)

最后,在DemoDlg.cpp文件中编写OnThreadFinished()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
void CDemoDlg::OnBtnTest() 
{
// 构造启动线程的参数
THREADPARMS* ptp = new THREADPARMS;
ptp->hWnd = m_hWnd;
ptp->BeginNumb = 1;
ptp->EndNumb = 100;

// 启动计算线程
AfxBeginThread (ThreadFunc, ptp);

GetDlgItem(IDC_EDT_OUT)->SetWindowText("测试开始");
}

6) 多线程运行结果

编译运行,对话框可以随意拖动不再卡了,使用Process Hacker查看Demo.exe进程,可以发现当点击测试按钮时,它会创建一条新线程。试着多点几次测试,会发生什么?

多线程运行

三、参考内容

关于多线程详细的内容,请参考 http://www.cnblogs.com/TenosDoIt/archive/2013/04/15/3022092.html 或者善用搜索引擎。

四、源码下载

Demo源码百度网盘下载: http://pan.baidu.com/s/1qW5kwsk 密码: 3u5i