`

MFC 界面线程和工作者线程

 
阅读更多

MFC 界面线程和工作者线程

每个系统都有线程,而线程的最重要的作用就是并行处理,提高软件的并发率。针对界面来说,还能提高界面的响应力。

线程分为界面线程和工作者线程,界面实际就是一个线程画出来的东西,这个线程维护一个“消息队列”,“消息队列”也是界面线程和工作者线程的最大区别,这个词应该进到你的脑子里,根深蒂固的!

如果在界面线程的某个地方停住,这说明它处理不了窗口消息了,所以有时候我们就会看到整个界面无响应了。这种问题后面会提供一个叫 WaitForObjectEx 的函数来解决,我们后面再谈。

线程首先就是它的创建,创建是用下面这个函数:CreateThread; 具体的参数我不说了,自己查MSDN。其中的 Thread1 是线程函数。线程函数是一个全局函数,如下:

DWORD WINAPI Thread1(LPVOID lpParam)
{
while(1)
{
OutputDebugString("11111");

Sleep(10);
}
return 0;
}

// 下面这一句是创建线程
CreateThread(NULL, 0, Thread1, 0, 0, NULL);

当然我们不能让一个线程自生自灭,那样有可能在你退出程序的时候出现一些莫名其妙的问题,或者丢失一些数据,或者给你弹一个崩溃的对话框等等。。。

所以我们就要对这个线程进行管理,首先就是让它退出。

我们给它的while加上一个 BOOL 变量 g_bExitThread的判断,这样的话,线程函数就变成下面这样:

DWORD WINAPI Thread1(LPVOID lpParam)
{
while(!g_bExitThread)
{
OutputDebugString("11111");

Sleep(10);
}
return 0;
}

然后在需要它退出的时候把g_bExitThread设为TRUE,表示,喂,兄弟,你该退出了。

当然我们还要知道它是否成功退出了,因为线程句柄是一个内核对象,所以我们就要用到Windows的WaitForSingleObject来等待了。创建的时候和等待它退出的代码就要改变了,多了一个 HANDLE g_hTrd的变量:

// 创建
g_bExitThread = FALSE;
g_hTrd = CreateThread(NULL, 0, Thread1, 0, 0, NULL);

// 等待线程结束
g_bExitThread = TRUE;

if(g_hTrd != NULL)
{
DWORD dwRet = WaitForSingleObject(g_hTrd, 5000);
if(dwRet == WAIT_OBJECT_0)
{
AfxMessageBox("Thread exit success!");
}
else
{
DWORD dwRet = 0;
GetExitCodeThread(g_hTrd, &dwRet);
TerminateThread(g_hTrd, dwRet);
AfxMessageBox("Threadexit, but not all ok!");
}
CloseHandle(g_hTrd);
g_hTrd = NULL;
}

上面说了在界面线程里等待别的线程结束,也就是使用 WaitForSingleObject 的时候会阻塞整个窗口消息的处理,所以我们如果在界面线程里要等待别的内核对象时,我们要采用这种“等一下,处理一下界面消息”的方法。我已经写好了一个 WaitForObjectEx 的函数,如下:

// 此函数只能用于界面线程
static DWORD WaitForObjectEx( HANDLE hHandle, DWORD dwMilliseconds )
{
BOOL bRet;
MSG msg;
INT iWaitRet;
int nTimeOut = 0;
while( (bRet = ::GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if(nTimeOut++ * 20 >= dwMilliseconds)
break;

iWaitRet = WaitForSingleObject(hHandle, 20);
if(iWaitRet != WAIT_TIMEOUT)
{
break;
}
if (bRet == -1)
{
break;
}
else
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}

return iWaitRet;
}

很多时候,我们不想把线程作为一个全局函数来使用,所以这个时候我们把线程作为一个类的静态成员对象来写。当然也不能少了刚才的两个变量:退出标志和线程句柄。(设这个类是CTestThreadDlg)

// H 文件
BOOL m_bExitThread;
HANDLE m_hTrd;
static DWORD WINAPI Thread1(LPVOID lpParam);

// CPP文件,创建的时候把 this 指针传进去,因为类静态成员函数不能访问类的非静态成员,没有this指针
//(C++的知识点)
m_bExitThread = FALSE;
m_hTrd = CreateThread(NULL, 0, Thread1, this, 0, NULL);

线程函数变成了:

DWORD WINAPI CTestThreadDlg::Thread1(LPVOID lpParam)
{
CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam;
while(!pDlg->m_bExitThread)
{
OutputDebugString("11111");

Sleep(10);
}
return 0;
}

当有几个线程一起跑的时候,我们就要注意线程的同步问题了,线程的同步一般来说,是在多个线程共用了资源的时候。比如两个线程都用到了同一个VECTOR,都对VECTOR进行插入操作,不幸的是,VECTOR不是线程安全的,这个时候程序就会崩溃,所以我们就要对VECTOR这个资源做同步,同步的意思是“我访问的时候,你等待”。程序大致如下:

DWORD WINAPI CTestThreadDlg::Thread1(LPVOID lpParam)
{
CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam;
while(!pDlg->m_bExitThread)
{
OutputDebugString("11111");

pDlg->m_csForVec.Lock();
pDlg->m_vecTest.push_back("111");
pDlg->m_csForVec.Unlock();

Sleep(10);
}
return 0;
}

DWORD WINAPI CTestThreadDlg::Thread2(LPVOID lpParam)
{
CTestThreadDlg *pDlg = (CTestThreadDlg*)lpParam;
while(!pDlg->m_bExitThread2)
{
OutputDebugString("222");

pDlg->m_csForVec.Lock();
pDlg->m_vecTest.push_back("222");
pDlg->m_csForVec.Unlock();

Sleep(10);
}
return 0;
}

m_csForVec 是一个CCriticalSection变量,这个同步对象和其他的同步变量(事件、信号量、互斥区等)有一些不一样,例如只能在同一个进程的线程间访问、在操作系统的用户态访问,其他的必须进入核心态。所以这样导致了这种关键区的核心对象的速度要比其他的快100倍左右。。。

上面已经说了线程的创建、管理(退出线程、等待线程)、同步等,那我们发现了什么共性呢?作为一个程序员,我们要很敏感的发现这些代码上的共性,这是我们设计代码的主要前提。

首先我们发现上面的线程都有两个变量:
BOOL m_bExitThread; // 让线程退出的标志
HANDLE m_hTrd; // 线程句柄

另外我们WaitForSingleObject 的时候不能无限等待,所以要多一个 DWORD m_dwWaitTimeOut;

由于我想把线程启动和结束封装起来,所以我设计了这几个接口:

BOOL Start(LPVOID lpParam); // 启动线程,线程所需要的参数从这里传进
BOOL End(); // 结束线程
virtual void Run(); // 重写Run函数

所以整个的线程封装成以下的类:

// MyThread.h

#ifndef MY_THREAD_H
#define MY_THREAD_H

class CMyThread
{
public:
CMyThread();
virtual ~CMyThread();

BOOL Start(LPVOID lpParam);
BOOL End();
virtual void Run();

protected:
static DWORD WINAPI Thread(LPVOID lpParam);
void RunOnceEnd();

DWORD m_dwWaitTimeOut;
BOOL m_bExitThread;
HANDLE m_hTrd;
LPVOID m_lpParam;
};

#endif

// MyThread.Cpp

#include "stdafx.h"
#include "MyThread.h"
/////////////////////////////////////////////////////////////////////////////
// CMyThread
CMyThread::CMyThread()
{
m_bExitThread = FALSE;
m_hTrd = NULL;
m_dwWaitTimeOut = 5000;
}

CMyThread::~CMyThread()
{

}

BOOL CMyThread::Start(LPVOID lpParam)
{
m_lpParam = lpParam;
m_bExitThread = FALSE;
m_hTrd = CreateThread(NULL, 0, Thread, this, 0, NULL);

return TRUE;
}

BOOL CMyThread::End()
{
m_bExitThread = TRUE;

if(m_hTrd != NULL)
{
DWORD dwRet = WaitForSingleObject(m_hTrd, m_dwWaitTimeOut);
if(dwRet == WAIT_OBJECT_0)
{
AfxMessageBox("Thread exit success!");
}
else
{
DWORD dwRet = 0;
GetExitCodeThread(m_hTrd, &dwRet);
TerminateThread(m_hTrd, dwRet);
AfxMessageBox("Thread fucking exit!");
}

CloseHandle(m_hTrd);
m_hTrd = NULL;
}

return TRUE;
}

DWORD WINAPI CMyThread::Thread(LPVOID lpParam)
{
CMyThread *pTrd = (CMyThread *)lpParam;

while(!pTrd->m_bExitThread)
{
pTrd->Run();
}

return 0;
}

void CMyThread::RunOnceEnd()
{
m_bExitThread = TRUE;
CloseHandle(m_hTrd);
m_hTrd = NULL;
}

void CMyThread::Run()
{
}

我们需要写我们自己的线程的时候就重载一下这个Run函数

// 派生出一个类
class CMyThread1 : public CMyThread
{
public:
virtual void Run();
};

// 改写Run函数
void CMyThread1::Run()
{
CTestThreadDlg *pDlg = (CTestThreadDlg *)m_lpParam;

OutputDebugString("222");

pDlg->m_csForVec.Lock();
pDlg->m_vecTest.push_back("222");
pDlg->m_csForVec.Unlock();

Sleep(10);

// 如果此线程只想运行一次,加上下面这句
RunOnceEnd();
}

然后我们之前的两个线程的使用就变成了下面的形式:

CMyThread1 g_t1, g_t2, g_t3;
void CTestThreadDlg::OnButton3()
{
g_t1.Start(this);
g_t2.Start(this);
g_t3.Start(this);
}

void CTestThreadDlg::OnButton4()
{
g_t1.End();
g_t2.End();
g_t3.End();
}


只需要以下几步:
1、派生自己的线程类
2、重载Run函数
3、调用Start启动线程
4、调用End结束线程

http://blog.csdn.net/dylgsy/article/details/2176160

http://blog.sina.com.cn/s/blog_4d6c5b5e01000986.html

工作线程与界面进程

在MFC程序中创建一个线程,宜调用AfxBeginThread函数。该函数因参数不同而具有两种重载版本,分别对应工作者线程和用户接口(UI)线程,关于UI线程和工作者线程的分配,最好的做法是:将所有与UI相关的操作放入主线程,其它的纯粹的运算工作交给独立的数个工作者线程。


用户接口(UI)线程:主要是用于处理用户的输入并响应各种事件和消息,他是由CWinThread派生出来的
工作者线程:进行程序的后台任务,如计算调度等,它不需要重CWinThread类派生出来进行创建,它其实就是一个函数,这个函数完成该线程并行的工作,由其它语句调用这个线程启动。
 工作者线程

CWinThread *AfxBeginThread(
 AFX_THREADPROC pfnThreadProc, //控制函数
 LPVOID pParam, //传递给控制函数的参数
 int nPriority = THREAD_PRIORITY_NORMAL, //线程的优先级
 UINT nStackSize = 0, //线程的堆栈大小
 DWORD dwCreateFlags = 0, //线程的创建标志
 LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程的安全属性
);

  工作者线程编程较为简单,只需编写线程控制函数和启动线程即可。下面的代码给出了定义一个控制函数和启动它的过程:

//线程控制函数
UINT MfcThreadProc(LPVOID lpParam)
{
 CExampleClass *lpObject = (CExampleClass*)lpParam;<wbr>//定义一个函数指针来指向调用AfxBeginThread的命令所在的类<br>  if (lpObject == NULL || !lpObject-&gt;IsKindof(RUNTIME_CLASS(CExampleClass)))<br>   return - 1; //输入参数非法<br>  //线程成功启动<br>  while (1)<br>  {<br>   ...//<br>  }<br>  return 0;<br> }<br></wbr>
<wbr><br> eg:<br> 建立基于对话框的工程,建立控制函数:ThreadProc.h和ThreadProc.cpp以及建立一个基于CWinThread的类CMyUI;<br> Cmytest17Dlg.CPP:<br> void Cmytest17Dlg::OnBnClickedButton1()<br> {<br><wbr>// TODO: 在此添加控件通知处理程序代码<br><wbr><wbr><wbr><wbr><wbr><wbr><wbr>CMyUI *m_pMyUIThread;<wbr>//UI线程<br><wbr>CWinThread *myThread;<br><wbr>m_pMyUIThread = (CMyUI *) AfxBeginThread(RUNTIME_CLASS(CMyUI));<wbr>//开始一个UI线程<br><wbr>m_pMyUIThread-&gt;PostThreadMessage(WM_MYMSG_ONE,NULL,NULL);<wbr><wbr><wbr><wbr><wbr><wbr><wbr>//对UI线程发送消息<br><wbr><wbr><wbr><wbr><wbr><wbr><wbr>myThread = AfxBeginThread((AFX_THREADPROC) ThreadPro,this);<wbr><wbr><wbr><wbr><wbr>//开始一个工作者线程<br><wbr>myThread = NULL;<br><wbr><wbr><wbr><wbr><wbr><wbr><wbr>m_pMyUIThread = NULL;<br><wbr><br> }</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

ThreadProc.h
UINT ThreadPro(LPVOID wParam);

ThreadProc.cpp
#include "stdafx.h"
#include "ThreadProc.h"
#include "mytest17Dlg.h"
#include "MyUI.h"

UINT ThreadPro(LPVOID wParam)
{
<wbr>Cmytest17Dlg *pDlg = (Cmytest17Dlg *) wParam;<br><wbr>pDlg-&gt;m_pMyUIThread-&gt;PostThreadMessage(WM_MYMSG_ONE,NULL,NULL);<br><wbr>AfxEndThread(0);<wbr>//执行完在这里结束线程序<br><wbr>return 1;<br> }</wbr></wbr></wbr></wbr></wbr>


MyUI.h
pragma once
#define WM_MYMSG_ONE<wbr><wbr>(WM_USER + 1)<wbr>//定义两个消息<br> #define WM_MYMSG_TWO<wbr><wbr>(WM_USER + 2)</wbr></wbr></wbr></wbr></wbr>


// CMyUI

class CMyUI : public CWinThread
{
<wbr>DECLARE_DYNCREATE(CMyUI)</wbr>

protected:
<wbr>CMyUI();<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>// 动态创建所使用的受保护的构造函数<br><wbr>virtual ~CMyUI();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

public:
<wbr>virtual BOOL InitInstance();<br><wbr>virtual int ExitInstance();<br><wbr>afx_msg void MsgOneProc(UINT wParam, LONG lParam);<wbr><wbr>//定义消息处理函数,头要用afx_msg<wbr>参数要用UINT wParam, LONG //lParam<br><wbr>afx_msg void MsgTwoProc(UINT wParam, LONG lParam);</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

protected:
<wbr>DECLARE_MESSAGE_MAP()<br> };</wbr>


MyUI.CPP

#include "stdafx.h"
#include "mytest17.h"
#include "MyUI.h"


// CMyUI

void CMyUI::MsgOneProc(UINT mParam, LONG lParam)<wbr><wbr>//前面不用加afx_msg<br> {<br><wbr>AfxMessageBox("this is message process one",MB_OK);<br><wbr>return;<br> }</wbr></wbr></wbr></wbr>

void CMyUI::MsgTwoProc(UINT mParam, LONG lParam)
{
<wbr>AfxMessageBox("this is message process two",MB_OK);<br><wbr>return;<br> }</wbr></wbr>

IMPLEMENT_DYNCREATE(CMyUI, CWinThread)

CMyUI::CMyUI()
{
}

CMyUI::~CMyUI()
{
<wbr>AfxEndThread(0);<br> }</wbr>

BOOL CMyUI::InitInstance()
{
<wbr>// TODO: 在此执行任意逐线程初始化<br><wbr>return TRUE;<br> }</wbr></wbr>

int CMyUI::ExitInstance()
{
<wbr>// TODO: 在此执行任意逐线程清理<br><wbr>return CWinThread::ExitInstance();<br> }</wbr></wbr>

BEGIN_MESSAGE_MAP(CMyUI, CWinThread)
<wbr>ON_THREAD_MESSAGE(WM_MYMSG_ONE,MsgOneProc)<wbr><wbr>//消息映射表:前面加ON_THREAD_MESSAGE(消息名,消息处理函数)<br><wbr>ON_THREAD_MESSAGE(WM_MYMSG_TWO,MsgTwoProc)<wbr><wbr>//注意这里是ON_THREAD_MESSAGE<br> END_MESSAGE_MAP()</wbr></wbr></wbr></wbr></wbr></wbr>

 

转载自:http://zhou24388.blog.163.com/blog/static/59746327201172992145405/

分享到:
评论

相关推荐

    MFC多线程 工作者线程 用户界面线程

    MFC多线程 工作者线程 用户界面线程

    MFC多线程的创建,包括工作线程和用户界面线程

    《MFC多线程的创建,包括工作线程和用户界面线程》全面讲解MFC多线程的创建,界面多线程与工作者多线程,多线程的起源、继承与派生,两多线程之间的区别与相同点,定时器与多线程的关系与异同(定时器是定时优先抢占...

    MFC用户界面线程与工作者线程

    工作者线程创建滚动条,用户界面线程实现绘图功能,思路清晰,对线程初学者有很大帮助

    MFC创建多线程(工作者线程)demo

    如标题,在MFC中必须使用 AfxBeginThread创建多线程,如使用BeginThread可能会出现BUG

    MFC 工作者线程 线程

    利用工作者线程实现的一个累加程序,输入数据,工作者线程负责计算

    Mutex_Critical.rar_MUTEX算法_event_mfc 控制 界面_mfc中控_界面线程

    这个例子利用多线程使用不同的...而且MFC还把线程分成用户界面线程和工作者线程,实质上用户界面线程跟工作者线程的差别是,用户界面线程要继承的基类已经实现了消息循环,MFC帮你做了很多的消息处理和界面控制的工作。

    MFC工作者线程实例

    对于工作者线程的启动,暂停和退出做的小例子!有中文注释

    MFC创建工作者线程.doc

    MFC多线程编程方面的资料,希望对大家有用; 彻底共享,决不要分!

    基于c/c++的MFC的多线程

    该函数因参数不同而具有两种重载版本,分别对应工作者线程和用户接口(UI)线程。  工作者线程 CWinThread *AfxBeginThread(  AFX_THREADPROC pfnThreadProc, //控制函数  LPVOID pParam, //传递给控制函数的...

    MFC多线程总结.doc

    MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。   工作者线程没有消息机制,通常用来执行后台计算和维护任务,如...

    (简洁)工作者线程,用户界面线程,主线程学习代码

    mfc vs2010版本,自己写的学习工作者线程,用户界面线程的代码.带有部分注释,希望对初学者有帮助(&gt;_&lt;我也是初学)

    MFC对多线程编程的支持

    MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。 工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长...

    深入浅出MFC多线程

    该函数因参数不同而具有两种重载版本,分别对应工作者线程和用户接口(UI)线程。  工作者线程 CWinThread *AfxBeginThread(  AFX_THREADPROC pfnThreadProc, //控制函数  LPVOID pParam, //传递给控制函数的...

    MFC多线程代码

    包含了工作者线程和用户界面线程的例子。可参考http://blog.csdn.net/tfygg/article/details/49839967

    多线程开发--基本多线程

     MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。  工作者线程没有消息机制,通常用来执行后台计算和维护任务,如...

    MFC 多线程.7z

    对于工作者线程的启动,暂停和退出做的小例子.实现线程的几种调用,实现多线程的简单应用。

    多线程编程实例

    MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

    mfc教程(word版)

    8.5.2 创建工作者线程 155 8.5.3 AfxBeginThread 155 8.5.4 CreateThread和_AfxThreadEntry 157 8.5.5 线程的结束 160 8.5.6 实现线程的消息循环 161 第9章 MFC的状态 163 9.1 模块状态 163 9.2 模块、进程和线程...

    VC.MFC套接字socket网络编程,完整代码高速文件传输,多线程,可直接生成使用,VS2010

    采用VS2010,基于MFC实现大文件高速传输,socket同步异步灵活切换使用,多进程。...非常适合初学者和有一定基础的提高者。自定义对话框工具,设计完美,界面美观。学习交流,拒绝商用。宸宸工作室。

Global site tag (gtag.js) - Google Analytics