您的位置:首页 > 其它

MFC线程有关问题

2014-04-10 17:37 176 查看
AfxBeginThread
用户界面线程和工作者线程都是由AfxBeginThread创建的。现在,考察该函数:MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另一个用于工作者线程,分别有如下的原型和过程:
用户界面线程的AfxBeginThread
用户界面线程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
其中:
参数1是从CWinThread派生的RUNTIME_CLASS类;
参数2指定线程优先级,如果为0,则与创建该线程的线程相同;
参数3指定线程的堆栈大小,如果为0,则与创建该线程的线程相同;
参数4是一个创建标识,如果是CREATE_SUSPENDED,则在悬挂状态创建线程,在线程创建后线程挂起,否则线程在创建后开始线程的执行。
参数5表示线程的安全属性,NT下有用。

工作者线程的AfxBeginThread
工作者线程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
其中:
参数1 线程的入口函数,声明一定要如下: UINT MyThreadFunction( LPVOID pParam );
参数2 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.
参数3、4、5分别指定线程的优先级、堆栈大小、创建标识、安全属性,含义同用户界面线程。

附录A:
结束线程的两种方式
当你在后台用线程来打印一些图形时.有时在打印一部分后,你希望可以停下来,那么此如何让线程停止呢.下
面会详细的向你解释要结束线程的两种方式
1 : 这是最简单的方式,也就是让线程函数执行完成,此时线程正常结束.它会返回一个值,一般0是成功结束,
当然你可以定义自己的认为合适的值来代表线程成功执行.在线程内调用AfxEndThread将会直接结束线程,此时线程的一切资源都会被回收.
2 : 如果你想让别一个线程B来结束线程A,那么,你就需要在这两个线程中传递信息.不管是工作者线程还是界面线程,如果你想在线程结束后得到它的确结果,那么你可以调用 ::GetExitCodeThread函数

还是老师的那个项目,以前由于计算量太大,导致程序经常出现假死的现象,因为程序只有一个线程,该线程主要用于处理计算上了,而对于消息队列的响应被忽略了。因此解决的办法就是用两个线程,一个线程用于计算,一个线程用于处理消息。
到网上找了一些资料,发现在MFC中把线程分为两类,一类为界面线程,一类为工作线程。两者的区别在于前都能够处理消息响应,而后者则不能。对于该项目来说,只要把计算的过程放到一个工作线程里来进行就可以了。
现在先试一下,我新建了一个对话框,上面添加两个按钮,一个是start 一个是dialog。前者用于开始计算,而后者则弹出一个消息框。然后向该对话框里面添加一个死循环的函数
UINT CMultithreadDlg::jisuan(LPVOID lpParam)
{
int i = 1;
for (;;)
{
i+=i;
}
return 0;
}
然后在start按钮的响应函数上添加上jisuan(NULL);即可,现在运行程序,按下start按钮后,可以看到CPU使用率涨到了100%,这个时候再按dialog按钮无反应,拖动关闭窗口均无效。这就是前面提到的假死现象(实际上是真死,因为死循环了,如果不是死循环,而只是计算量太大才是假死)。
下面用多线程的方法来解决,在start按钮的响应函数改为
CWinThread* mythread = AfxBeginThread(
jisuan,
NULL,
THREAD_PRIORITY_NORMAL,
0,
0,
NULL
);
运行,结果发现有错error C2665: 'AfxBeginThread' : none of the 2 overloads can convert parameter 1 from type 'unsigned int (void *)' Generating Code...
我就纳闷了,函数指针是对的啊,原来 线程函数可以且必须是全局函数或者是静态成员函数。
所以我们在线程函数的声明中改为 static UINT jisuan(LPVOID lpParam);即可,然后运行程序,这时点击start,待CPU涨至100%后,点击dialog,弹出对话框了,拖动、关闭窗口均没问题了。
其实上面的那个AfxBeginThread,除前面两个参数外,后面的都是默认参数,可以省略。而必须有的这两个参数,一个是线程函数的指针,一个是传递给这个函数的参数。实际中我们经常这样用 AfxBeginThread(ThreadProc,this);//把this传过去,就可以调用类的成员了. 这样线程函数就可以使用和操作类的成员了。千万要注意线程函数是静态类函数成员。
线程是创建了,但是如果中途要暂停该怎么做呢?
我们在创建线程的时候获得了一个CWinThread的指针,这是一个指向线程对象的指针,CWinThread类里面就有暂停与恢复的函数,下面我就演示一下。
在原来的程序上进行改动。向对话框类里面添加一个CWinThread* 的成员变量,不用初始化为NULL,这样会报错的,因为它只能通过AfxBeginThread函数获得。把start里面的声明去掉。
然后添加一个 pause 按钮向其响应函数里面添加代码 mythread->SuspendThread(); 再添加一个 resume按钮,向其响应函数里面添加 mythread->ResumeThread();
再运行程序,我们start之后,按下pause可以看到CPU恢复正常,然后resume,CPU又涨上去了,到此证明一切操作正常。

MFC 界面线程和工作者线程

2011-08-29 09:21:45| 分类:计算机相关|举报|字号订阅
我的淘宝小店 欢迎大家光临 http://wangzaiqiqi.taobao.com
每个系统都有线程,而线程的最重要的作用就是并行处理,提高软件的并发率。针对界面来说,还能提高界面的响应力。
线程分为界面线程和工作者线程,界面实际就是一个线程画出来的东西,这个线程维护一个“消息队列”,“消息队列”也是界面线程和工作者线程的最大区别,这个词应该进到你的脑子里,根深蒂固的!
如果在界面线程的某个地方停住,这说明它处理不了窗口消息了,所以有时候我们就会看到整个界面无响应了。这种问题后面会提供一个叫 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("Thread exit, 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; //定义一个函数指针来指向调用AfxBeginThread的命令所在的类
 if (lpObject == NULL || !lpObject->IsKindof(RUNTIME_CLASS(CExampleClass)))
  return - 1; //输入参数非法
 //线程成功启动
 while (1)
 {
  ...//
 }
 return 0;
}

eg:
建立基于对话框的工程,建立控制函数:ThreadProc.h和ThreadProc.cpp以及建立一个基于CWinThread的类CMyUI;
Cmytest17Dlg.CPP:
void Cmytest17Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CMyUI *m_pMyUIThread; //UI线程
CWinThread *myThread;
m_pMyUIThread = (CMyUI *) AfxBeginThread(RUNTIME_CLASS(CMyUI)); //开始一个UI线程
m_pMyUIThread->PostThreadMessage(WM_MYMSG_ONE,NULL,NULL); //对UI线程发送消息
myThread = AfxBeginThread((AFX_THREADPROC) ThreadPro,this); //开始一个工作者线程
myThread = NULL;
m_pMyUIThread = NULL;

}
ThreadProc.h
UINT ThreadPro(LPVOID wParam);
ThreadProc.cpp
#include "stdafx.h"
#include "ThreadProc.h"
#include "mytest17Dlg.h"
#include "MyUI.h"
UINT ThreadPro(LPVOID wParam)
{
Cmytest17Dlg *pDlg = (Cmytest17Dlg *) wParam;
pDlg->m_pMyUIThread->PostThreadMessage(WM_MYMSG_ONE,NULL,NULL);
AfxEndThread(0); //执行完在这里结束线程序
return 1;
}

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

// CMyUI
class CMyUI : public CWinThread
{
DECLARE_DYNCREATE(CMyUI)
protected:
CMyUI(); // 动态创建所使用的受保护的构造函数
virtual ~CMyUI();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
afx_msg void MsgOneProc(UINT wParam, LONG lParam); //定义消息处理函数,头要用afx_msg 参数要用UINT wParam, LONG //lParam
afx_msg void MsgTwoProc(UINT wParam, LONG lParam);
protected:
DECLARE_MESSAGE_MAP()
};

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

// CMyUI
void CMyUI::MsgOneProc(UINT mParam, LONG lParam) //前面不用加afx_msg
{
AfxMessageBox("this is message process one",MB_OK);
return;
}
void CMyUI::MsgTwoProc(UINT mParam, LONG lParam)
{
AfxMessageBox("this is message process two",MB_OK);
return;
}
IMPLEMENT_DYNCREATE(CMyUI, CWinThread)
CMyUI::CMyUI()
{
}
CMyUI::~CMyUI()
{
AfxEndThread(0);
}
BOOL CMyUI::InitInstance()
{
// TODO: 在此执行任意逐线程初始化
return TRUE;
}
int CMyUI::ExitInstance()
{
// TODO: 在此执行任意逐线程清理
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CMyUI, CWinThread)
ON_THREAD_MESSAGE(WM_MYMSG_ONE,MsgOneProc) //消息映射表:前面加ON_THREAD_MESSAGE(消息名,消息处理函数)
ON_THREAD_MESSAGE(WM_MYMSG_TWO,MsgTwoProc) //注意这里是ON_THREAD_MESSAGE
END_MESSAGE_MAP()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  MFC线程