win32多线程-在MFC程序中使用多线程

1、基础知识介绍

    使用mfc的道友,应该很清楚,在mfc程序中创建多线程时,应该调用mfc中为我们设计好的线程接口函数AfxBeginThread(),原因是这个接口为我们做好了mfc函数和数据的初始化工作,如果你的mfc多线程中不使用任何mfc函数或数据,你也可以不用此函数来创建多线程。好了,进入正题,我来介绍一下用AfxBeginThread()创建worker线程和UI线程的两种调用方式。


    用户界面线程和工作者线程都是由AfxBeginThread创建的。MFC提供了两个重载版的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分别指定线程的优先级、堆栈大小、创建标识、安全属性,含义同用户界面线程。

用户界面线程的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()函数的返回值是CWinThread* 指针,但是这个指针不能直接使用,因为这个指针会自动销毁。如果道友直接使用了这个指针,那么当在操作这个指针时,若已被mfc销毁,那么访问违规将会到来。
至于返回值的使用请看,我写的一个mfc程序片段。
CString strName = _T("");
CWinThread* pThread = NULL;
UINT CBcgTestDlg::ThreadWorkFunc(LPVOID lPvoid)
{
	
	for (int n = 0; n < 10000; n++)
	{
		strName = _T("http://blog.csdn.net/windows_nt");
		strName = strName + _T("\n");
		TRACE(strName);
	}
	
	return 0;
}


void CBcgTestDlg::OnOK() 
{
	for (int n = 0; n < 10000; ++n)
	{
		if (pThread)
		{
			WaitForSingleObject(pThread->m_hThread, INFINITE);
			delete pThread;
		}
		
		pThread = AfxBeginThread(ThreadWorkFunc, NULL, 0, CREATE_SUSPENDED, NULL);
		if (pThread)
		{
			pThread->m_bAutoDelete = FALSE;
			pThread->ResumeThread();
		}
	}
}

//现在可以放心的使用返回值pThread了,但是要记得在使用结束后记得调用delete pThread,释放资源(CWinThread类中的线程句柄会在析构函数中自动释放)。

2、mfc中使用多线程要点

工作者线程的使用和c++中使用多线程没什么区别,请参考我以前写的四种线程同步的方式:
windows核心编程-关键段(临界区)线程同步
windows核心编程-互斥器(Mutexes)
windows核心编程-信号量(semaphore)
windows核心编程-内核对象线程同步

接下来我介绍一下UI线程的一些相关知识。
    AfxBeginThread()的UI线程版本,期望为你在pThreadClass参数中所指定的类配置一个对象,此类必须派生自CWinThread,CWinThread提供了一堆多样化的虚函数,你可以改写之以帮助消息的处理,线程的启动和清理,以及异常情况的处理,这些虚函数列举如下:
InitInstance()
ExitInstance()
OnIdle()
PreTranslateMessage()
IsIdleMessage()
ProcessWndProcException()
ProcessMessageFilter()
Run()
当然,你也并不是非要改写它们不可。默认情况下只要改写InitInstance(),mfc就会开启一个消息循环。


3、mfc中多线程mfc数据共享的限制

    mfc多线程程序有一个重大限制,会影响你所做的几乎每一件事情。mfc各对象和win32 handles之间的映射关系记录在线程局部存储(Thread Local Storage, SLS)之中。因此,你没有办法把一个mfc对象从某线程手上交到另一线程手上,你也不能够在线程之间传递mfc对象指针。我所谓的mfc对象包括(但不限于)CWnd、CDC、CPen、CBrush、CFont、CBitmap、CPalette、这个限制的存在阻止了“为这些对象产生同步机制”的必要性,那会大大影响mfc的速度效率。

    这个限制有几个分歧,如果两个线程都调用CWnd::GetDlgItem()以取得对话框中的一个控件(例如 edit),那么每个线程应该获得不同的指针——甚至即使两个线程的对象是一个控件。如果面对一个指针,其所指对象并没有永久的mfc结构,那么当对此指针的一个索求行为出现时,mfc往往会产生一些临时性对象。例如,CWnd::GetDlgItem()往往会在被索求一个指针(例如指向一个CEdit*或一个CStatic*)时,产生一个临时性对象。这些对象会在下一次程序进入闲置循环时被清理掉。如果这些对象被许多线程共享,MFC就没有能力预期它们的生命,也因此没有能力执行清理工作。因此,mfc为每一个有需求的线程产生一个新对象。
    这个限制(关于在线程之间交换对象)的意思是说,你不能够放一个指针(指向一个CWnd)到结构之中,而该结构被一个Worker用,你也不能够把一个指向CDialog或CView的指针交给另一个线程。当你需要调用View或document中的一个成员函数,特别是像UpdateAllViews()这样的函数时,上述的限制很快就会恶化。
    MFC在许多地方检查"横跨线程之对象的使用情况"。任何时候,只要MFC对着对象调用ASSERT_VALID,它便会检查对象是否保持在线程局部存储(TLS)中,如果你尝试调用一个像CDocument::UpdateAllViews()这样的函数,那么当程序运行时,CWnd::AssertValid()会产生一个assertion.

下边的注释出现在MFC源代码的WINCORE.CPP中的Cwnd::ASSERT_VALID()成员函数内。
// Note: if either of the above asserts fire and you are
// writing a multithreaded application, it is likely that
// you have passed a C++ object from one thread to another
// and have used that object in a way that was not intended.
// (only simple inline wrapper functions should be used)
//
// In general, CWnd objects should be passed by HWND from
// one thread to another.  The receiving thread can wrap
// the HWND with a CWnd object by using CWnd::FromHandle.
//
// It is dangerous to pass C++ objects from one thread to
// another, unless the objects are designed to be used in
// such a manner.
    线程局部存储(TLS)的使用说明了以AfxBeginThread()在MFC的程序中产生UI线程的重要性。如果你用的是_beginthreadex()或CreatThread()时,mfc不会给你机会产生出用以维护其handles的必要结构。
    在线程之间共享对象,这里倒是有一个不太方便的替代方案:不要放置MFC对象,改放对象的handle。当你把handle传递给新线程时,线程可以把该handle附着到一个新的mfc对象:使用FromHandle()可以产生一个临时对象,使用Attach()则可以产生一个永久对象。例如,你把一个HDC交给线程,你可以利用以下程序代码把这个HDC附着到一个永久的CDC对象上:

//1、长时间使用
HDC hOriginalDC; //收到的HDC
CDC dc;
dc.Attach(hOriginalDC);
//在此进行绘图操作
dc.Detach();//线程退出前应该调用Detach();
//2、如果线程只是想短暂地使用这个数值,它可以产生一个临时对象,像这样:
//CDC *pDC = CDC::FromHandle(hOriginalDC);
    并不是所有的mfc对象都很容易以该技术传递。CView就是个例子,你可以轻易取得一个view的窗口handle,并将它交给一个新线程,但最好这个新线程可以把此handle附着到一个CWnd,因为并没有CView::FromHandle()函数可以产生一个临时性的view,像镜子一样反映出原来的那一个。原来的CView结构不再可用。所以所有相关的view信息也都不再可用了。回到UpdateAllViews(),此函数运作时所使用的指针被埋藏在document中,无法改变。因此,其他线程没有任何方法可以调用此函数。唯一一个替代方案就是送出一个用户自定义消息,回到原线程中,告诉它更新它的views。

你可能感兴趣的:(win32多线程-在MFC程序中使用多线程)