1.进度条
1.1 我们创建一个基于对话框的MFC应用程序
1.给对话框添加一个 Progress Control 控件,添加一个 安装 按钮;
2.给 安装 按钮添加事件处理函数,该函数是为了当我们点击 安装 按钮时,进度条能够开始跑起来;
3.给进度条添加一个变量: CProgressCtrl m_process_ctrl ;
4.使用 SetIt 函数来使进度条跑起来;
void CThreadDlg::OnBnClickedButton1()
{
// 1. 让进度条动起来
m_process_ctrl.SetStep(1); // 设置步长
while(1)
{
m_process_ctrl.StepIt();
::Sleep(200);
}
}
1.2 只让进度条跑一截距离,而不让其跑完
1.当进度条的位置大于某个值时将其置零
void CThreadDlg::OnBnClickedButton1()
{
// 2. 让进度条动起来
m_process_ctrl.SetStep(1); // 设置步长
while(1)
{
if(m_process_ctrl.GetPos() > 50)
m_process_ctrl.SetPos(0);
m_process_ctrl.StepIt();
::Sleep(200);
}
}
1.3 我们发现在这个函数运行过程中,我们无法拖动进度条窗口
1.这是因为我们在处理消息的过程中会一直停留在 CThreadDlg::OnBnClickedButton1 消息中,因为我们在这个函数中写了死循环;
2.我们在循环中设置一个消息来接受其他消息就可以拖拽对话框了;
void CThreadDlg::OnBnClickedButton1()
{
// 3. 在进度条动的过程中,可以拖拽对话框
m_process_ctrl.SetStep(1);
while (TRUE)
{
m_process_ctrl.StepIt();
::Sleep(200);
// 接受其他的消息
MSG msg;
::GetMessage(&msg, 0, 0, 0);
if(!AfxPreTranslateMessage(&msg)) // 预处理消息
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
3.完成上述代码后,我们又发现一个问题:当我们鼠标在对话框上移动时进度条才走,否则进度条还是不动,这个原因是 GetMessage 函数是阻塞的,也就是说它不接受到消息就会一直停在这一行代码;
4.我们使用 PeekMessage() 函数来非阻塞获取消息;
void CThreadDlg::OnBnClickedButton1()
{
// 3. 在进度条动的过程中,可以拖拽对话框
m_process_ctrl.SetStep(1);
while (TRUE)
{
m_process_ctrl.StepIt();
::Sleep(200);
// 接受其他的消息
MSG msg;
// ::GetMessage(&msg, 0, 0, 0);
::PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if(!AfxPreTranslateMessage(&msg)) // 预处理消息
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
2.线程
2.1 线程:轮换时间片的基本单位,进程中的执行单元
2.2 并发:多个线程交替执行
2.3 并行:多个线程同时执行
2.4 线程的组成部分
1.线程堆栈:存放局部临时资源
2.线程内核对象:计数器,挂起计数器,信号
3.使用多线程完成进度条
3.1 创建一个线程
1.使用函数 ::CreateThread 函数创建进程;
void CThreadDlg::OnBnClickedButton1()
{
if(thread_handle != 0)
{
return;
}
HANDLE thread_handle = ::CreateThread(
0, // 安全属性
0, // 线程栈的大小
&CThreadDlg::ThreadProc, // 线程处理函数
this, // 给线程处理函数传递的参数
0, // 创建的线程的状态, 0 代表立即运行
0 // 线程ID
);
if(thread_handle == 0)
{
MessageBox("线程创建失败");
}
}
2.其中的线程处理函数是一个静态函数: static DWORD WINAPI ThreadProc(In LPVOID lpParameter) ;
3.为了使非静态成员能够在静态函数中调用,我们获取主对话框的指针: CThreadDlg* pThis = (CThreadDlg*)lpParameter ;
// 线程处理函数(要用线程做什么事情)
DWORD WINAPI CThreadDlg::ThreadProc(_In_ LPVOID lpParameter)
{
CThreadDlg* pThis = (CThreadDlg*)lpParameter;
pThis->m_process_ctrl.SetStep(1);
while (pThis->m_quit_thread_flag)
{
pThis->m_process_ctrl.StepIt();
::Sleep(200); // 让出 时间片 函数
}
return 0;
}
4.完成上述操作即可通过新创建的线程来跑进度条。
3.2 暂停 按钮
1.给对话框添加一个 暂停 按钮;
2.在该函数中挂起线程,首先将线程的句柄定义为类成员;
// 暂停让进度条动
void CThreadDlg::OnBnClickedButton2()
{
if(thread_handle == 0)
return;
// 首先将线程的句柄定义为类成员
DWORD dwSuspendCount = SuspendThread(thread_handle); // 返回的是挂起之前的线程的个数
TRACE("挂起计数:%d\n", dwSuspendCount);
}
3.3 恢复 按钮
1.给对话框添加一个 恢复 按钮;
2.在该按钮处理函数中将挂起的线程重新运行;
// 继续让进度条动
void CThreadDlg::OnBnClickedButton3()
{
if(thread_handle == 0)
return;
DWORD dwResumeCount = ResumeThread(thread_handle);
TRACE("重新运行计数:%d\n", dwResumeCount);
}
3.4 结束线程 按钮
1.给对话框添加一个 结束线程 按钮;
2.给主对话框类添加一个成员: bool m_quit_thread_flag ,初始化为 true ;
3.将静态函数中的循环的条件更改为 m_quit_thread_flag ;
4.当点击 结束线程 时,将 m_quit_thread_flag 改为 false ;
// 结束按钮
void CThreadDlg::OnBnClickedButton4()
{
// 结束目前分两种情况
// 1. 改变flag可以退出
m_quit_thread_flag = false;
::CloseHandle(thread_handle);
thread_handle = 0;
}
5.改变flag无法退出(比如循环中又套了一个循环),这时需要直接终止线程;
// 结束按钮
void CThreadDlg::OnBnClickedButton4()
{
// 结束目前分两种情况
// 1. 改变flag可以退出
m_quit_thread_flag = false;
// 2. 改变flag无法退出(比如循环中又套了一个循环)
if(::WaitForSingleObject(thread_handle, 10) == WAIT_TIMEOUT) // 等待10毫秒超时
{
// 强制终止线程
::TerminateThread(thread_handle, -1); // 退出码给-1为了与正常退出进行区分
::CloseHandle(thread_handle);
thread_handle = 0;
}
}