MFC线程(一)简单介绍.doc_第1页
MFC线程(一)简单介绍.doc_第2页
MFC线程(一)简单介绍.doc_第3页
MFC线程(一)简单介绍.doc_第4页
MFC线程(一)简单介绍.doc_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

MFC线程(一):简单介绍线程简介我们知道一般情况程序中的代码都是按顺序从头开始一行一行的执行以最后.中间不能出现同时执行的情况.比如一段代码调用两个函数FunOne();FunTwo();只要当函数FunOne中的代码执行完才返回来执行FunTwo.假如逻辑上是有先后顺序那还真只能这样按顺序执行下来.不过有假如FunOne与FunTwo没有逻辑先后顺序,是相互独立的.比如两个函数分别处理两不同的文件one.text与two.txt.这种情形就可以用到线程,弄两个线程去执行这两函数.这样两函数同时执行,提高了效率(如果单核的CPU可能没有真正的并行效果不明显,那多核CPU执行多线程那是能够真正达到并行执行,效果很明显的).实际上可以这样简单的理解线程,它是CPU的调度单位.而一个线程是对应一个函数.所以别把一个线程想得太复杂,就只是执行个函数而已.只不过执行的时候是并行执行罢了.如果只是简单的几个线程不涉及使用共同的资源,没其他啥关联.就完全跟简单的执行一个函数类似.只是如果多个线程间关系复杂就会涉及到啥同步问题,那样就有很多复杂的细节性问题.线程与函数线程函数必须是全局函数,或者是类的静态成员函数,因为非静态成员函数有this指针,而在进程中无法访问此指针。但是静态成员函数只能访问静态成员,解决此问题途径:1. 就是在调用静态成员函数时将this指针作为参数传入,通过该指针访问非静态成员。2. 不将线程函数定义为类的静态成员函数,而是定义为类的友元函数,这样函数线程也可以有类成员函数相同的权限。最简单示例线程分工作线程与界面线程.这里就以工作线程为例1.先来看个MFC中的创建线程的简单例子.UINT ThreadFun(LPVOID pParam) /线程要调用的函数MessageBox(NULL,_T(i am called by a thread.), _T(thread func),MB_OK);:AfxBeginThread(ThreadFun, NULL); /这就是创建一个线程并执行了,调用上面的函数弹出一个对话框.2.示例分析上面的线程是简单的不能再简单了吧.下面从两个来分析下.a.首先是被调用的函数有啥讲究不? 当然有,被线程用到的函数格式必须是统一的,返回类型必须是UINT,函数只能有一个参数LPVOID.其中UINT就是个无符号的整形,LPVOID是void*,所以这个参数表示可以传任何类型的指针过来的.b.函数AfxBeginThread的分析.这个函数还有返回值CWinThread*的,如果你只是简单的创建一个线程并执行,就不用管了.但如果想要对创建的线程做其他操作就必须这样写.CWinThread* pThread = :AfxBeginThread(ThreadFun, NULL); /接下来做啥就直接调用pThead就行.另外函数AfxBeginThread的参数有很多个,但很多都有默认值.下面是完整的参数CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, /一个函数指针LPVOID pParam, /void*类型的指针,可以传任何种类指针过来.int nPriority = THREAD_PRIORITY_NORMAL, /线程优先级UNT nStackSize = 0,/分配堆栈大小DWORD dwCreateFlags = 0, /表示线程创建后是立即执行还是等会执行LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL /线程安全属性指针);/用于创建工作者线程上面的参数我们用的最多的是3个,其他一盘都默认值.AFX_THREADPROC pfnThreadProc /函数指针肯定是必须要指定的,不然线程执行哪个函数去啊LPVOID pParam /这是传给上面指定函数的参数.如果被调用的函数需要啥参数就只能在这里指定了.DWORD dwCreateFlags /默认值为0表示创建线程后立即执行.如果是CREATE_SUSPEND则表示创建好后先挂起.必须通过ResumeThread来执行.稍复杂点的例子扩充下上面的例子,给函数传入参数,并且休眠和挂起线程.UINT ThreadFun(LPVOID pParam) /线程要调用的函数int* pNum = (int*)pParam; /假如会传入一个整形指针参数MessageBox(NULL,_T(i am called by a thread.), _T(thread func),MB_OK);CWinThread* pThread; /定义一个线程指针void CreateThread()/创建一个线程并挂起int* pNum = new int(88); /传入的参数pThread = :AfxBeginThread(ThreadFun,pNum,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);void StartThread() /运行线程pThread-ResumeThread();/唤醒线程线程休眠与挂起的区别上面示例中先创建一个线程,并让它挂起.(suspend),被挂起的线程只有通过ResumeThread才能开始执行.而休眠则一般是这样使用.UINT ThreadFun(LPVOID pParam) /线程要调用的函数:Sleep(1000);/表示函数执行到这里先休息1000微秒,也就是1秒.然后再接着执行下面的语句.MessageBox(NULL,_T(i am called by a thread.), _T(thread func),MB_OK);所以休眠一般是会在线程调用的那个函数中指定.休眠在指定的时间后会自动再次执行,相当于暂停一断时间然后又自动活过来了,不用像挂起还必须得显式去启动才行.内核对象 进程内核对象这里的对象不不是指一个类的实例化,不过实际上也可以类似的等同.因为内核对象是内核分配的一个内存块,这种内存块就是一个结构体(struct).应用程序若需要访问内核对象需要通过一些API函数,不能直接访问(基于安全的考虑).内核对象的拥有者是内核,所以何时释放对象的内存是由内核决定的.我们使用内核对象时一般是通过一个句柄支间接的使用,于是每有一个句柄与对象关联则对象的引用计数加1,当系统发现内核对象的引用计数为0时则释放内核对象内存.(看起来是不是有点像智能指针的用法了啊?)进程与内核对象每个进程在初始化时被分配一个句柄表,表中保存进程能访问的所有内核对象的句柄(进程是不能直接访问内核对象,只能先在找到句柄表中的句柄,然后再使用内核对象.)当然进程还能通过CreateObject来创建一些内核对象,然后不使用时使用CloseHandle来关闭内核对象.如果某个进程创建内核对象时指定SECURITY_ATTRIBUTES中的bInheritHandle为TRUE,创建子进程时(CreateProcess也设bInheritHandle为TRUE)则子进程也能拥有那个内核对象的访问权限(此时子进程的句柄表会复制该内核对象句柄过来,内核对象引用计数加1).当然如果父进程在创建了子进程之后再生成一些内核对象,则子进程是不会继承那访问权限的.除了通过继承可以获得某个内核对象的访问权限外还可以通过同名共享(不过需要内核对象支持这种共享方式,不是所有种类的内核对象支持).当然通过CreateObject来创建好一个名为test1的内核对象后,此时如果有另外的进程再创建一个名为test1的内核对象那不会真的创建,而只是返回之前已创建好的test的句柄(看起来有点像是单例模式的应用啊)另外还可以通过复制内核对象的句柄,通过DuplicateHandle,当然了前提是进程要有对那个句柄的访问权限先.(在句柄表中有)线程与进程进程只是个容器,不会执行任何操作.它里面有很多线程(至少必须有一个主线程).进程内的所有线程共享进程的内核对象.当一个进程中止时所以线程自然中止./MFC启动和关闭线程1、启动线程:CWinThread* AfxBeginThread( 线程函数,this );2、通常导致线程终止的两种情况是:控制函数退出或不允许线程完成运行。如果字处理器使用后台打印线程,若成功完成打印,则控制函数将正常终止。但是,如果用户要取消打印,后台打印线程则不得不提前终止。本主题介绍如何实现每一种情况,以及在终止后如何获取线程的退出代码。(1)正常线程终止对于辅助线程,正常线程终止很简单:退出控制函数并返回表示终止原因的值。可以使用 函数或 return 语句。一般情况下,0 表示成功完成,但这取决于您自己。对于用户界面线程,该过程也很简单:从用户界面线程内调用 Platform SDK 中的 。PostQuitMessage 采用的唯一参数是线程的退出代码。对于辅助线程,0 通常表示成功完成。(2)过早的线程终止过早终止线程几乎一样简单:从线程内调用 。将所需的退出代码作为唯一参数传递。这将停止执行线程、解除对线程堆栈的分配、分离附加到线程的所有 DLL 并从内存中删除线程对象。必须从要终止的线程内调用 AfxEndThread。如果要从其他线程终止线程,必须设置两个线程间的通信方法。举一个例子:可以创建一个信号量,用WaitForSingleObject函数来检测该信号量的状态。成员变量 m_hThreadEvent;m_hThreadEvent = CreateEvent( NULL, FALSE, FALSE, NULL );线程的执行函数:for( ; ;) DWORD dwRetVal; dwRetVal = WaitForSingleObject( m_hThreadEvent, 100 ); if ( dwRetVal = WAIT_TIMEOUT ) / TODO: else / stop receive text thread. DWORD dwExitCode; GetExitCodeThread( m_pThreadRecv-m_hThread, &dwExitCode ); AfxEndThread( dwExitCode, TRUE ); 要结束线程时,使用SetEvent,将信号量置为有信号。该线程是在信号量有信号时,退出。(3)TerminateThread在CWinThread对象中有线程的句柄,可以使用该句柄强行杀死线程。但是不推荐使用这种方式,当可以正常结束的时候,选择前两种方法较好。检索线程的退出代码若要获取辅助线程或用户界面线程的退出代码,请调用 函数。有关此函数的信息,请参见 Platform SDK。此函数获取线程(存储在 CWinThread 对象的m_hThread数据成员中)的句柄和 DWORD 的地址。如果线程仍然是活动的,GetExitCodeThread 将 STILL_ACTIVE 放置在提供的 DWORD 地址中;否则将退出代码放置在该地址中。检索 对象的退出代码还需要一步。默认情况下,当 CWinThread 线程终止时,删除该线程对象。这意味着不能访问m_hThread数据成员,因为 CWinThread 对象不再存在。若要避免出现这种情况,请执行以下操作之一: 将m_bAutoDelete数据成员设置为 FALSE。这使 CWinThread 对象在线程终止后仍可以继续存在。然后可以在线程终止后,访问m_hThread数据成员。但是,如果使用此方法,就得销毁 CWinThread 对象,因为框架不会自动删除该对象。这是首选方法。 单独存储线程的句柄。创建线程后,(使用 :DuplicateHandle)将其m_hThread数据成员复制到其他变量,并通过该变量访问该成员。这样,终止后即会自动删除对象,并且仍然可以找到线程终止的原因。请注意:在可以复制句柄之前,线程不终止。执行此操作的最安全的方式是将 CREATE_SUSPENDED 传递到 ,存储句柄,然后通过调用 继续执行线程。任一方法都可以使您确定 CWinThread 对象终止的原因。/线程中CreateEvent和SetEvent及WaitForSingleObject的用法首先介绍CreateEvent是创建windows事件的意思,作用主要用在判断线程退出,线程锁定方面.CreateEvent函功能描述:创建或打开一个命名的或无名的事件对象.EVENT有两种状态:发信号,不发信号。SetEvent/ResetEvent分别将EVENT置为这两种状态分别是发信号与不发信号。WaitForSingleObject()等待,直到参数所指定的OBJECT成为发信号状态时才返回,OBJECT可以是EVENT,也可以是其它内核对象。当你创建一个线程时,其实那个线程是一个循环,不像上面 那样只运行一次的。这样就带来了一个问题,在那个死循环里要找到合适的条件退出那个死循环,那么是怎么样实现它的呢?在Windows里往往是采用事件的 方式,当然还可以采用其它的方式。在这里先介绍采用事件的方式来通知从线程运行函数退出来,它的实现原理是这样,在那个死循环里不断地使用 WaitForSingleObject函数来检查事件是否满足,如果满足就退出线程,不满足就继续运行。当在线程里运行阻塞的函数时,就需要在退出线程 时,先要把阻塞状态变成非阻塞状态,比如使用一个线程去接收网络数据,同时使用阻塞的SOCKET时,那么要先关闭SOCKET,再发送事件信号,才可以 退出线程的。当然我感觉重要应用方面还是用来锁定,实现所谓的pv功能。下面介绍函数功能,参数等1.CreateEvent函数功能描述:创建或打开一个命名的或无名的事件对象函数原型:HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, / 安全属性BOOL bManualReset, / 复位方式BOOL bInitialState, / 初始状态LPCTSTR lpName / 对象名称);参数:lpEventAttributes: 输入一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。 Windows NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。bManualReset: 输入指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。bInitialState: 输入指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。lpName: 输入指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。 如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于 bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是 参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。 如果lpName为NULL,将创建一个无名的事件对象。 如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。 终端服务(Terminal Services):名称中可以加入Global或是Local的前缀,这样可以明确的将对象创建在全局的或事务的命名空间。名称的其它部分 除了反斜杠(),可以使用任意字符。详细内容可参考Kernel Object Name Spaces。 Windows 2000:在Windows 2000系统中,没有终端服务运行,Global和Local前缀将被忽略。名称的其它部分除了反斜杠(),可以使用任意字符。 Windows NT 4.0以及早期版本, Windows 95/98:名称中除了反斜杠(),可以使用任意字符。返回值: 如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。 如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。备注: 调用CreateEvent函数返回的句柄,该句柄具有EVENT_ALL_ACCESS权限去访问新的事件对象,同时它可以在任何有此事件对象句柄的函数中使用。 在调用的过程中,所有线程都可以在一个等待函数中指定事件对象句柄。当指定的对象的状态被置为有信号状态时,单对象等待函数将返回。 对于多对象等待函数,可以指定为任意或所有指定的对象被置为有信号状态。当等待函数返回时,等待线程将被释放去继续运行。 初始状态在bInitialState参数中进行设置。使用SetEvent函数将事件对象的状态置为有信号状态。使用ResetEvent函数将事件对象的状态置为无信号状态。 当一个手动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至明确调用ResetEvent函数将其置为无符号状态。 当事件的对象被置为有信号状态时,任意数量的等待中线程,以及随后开始等待的线程均会被释放。 当一个自动复原的事件对象的状态被置为有信号状态时,该对象状态将一直保持有信号状态,直至一个等待线程被释放;系统将自动将此函数置为无符号状态。如果没有等待线程正在等待,事件对象的状态将保持有信号状态。 多个进程可持有同一个事件对象的多个句柄,可以通过使用此对象来实现进程间的同步。下面的对象共享机制是可行的: 在CreateEvent函数中,lpEventAttributes参数指定句柄可被继承时,通过CreateProcess函数创建的子进程继承的事件对象句柄。 一个进程可以在DuplicateHandle函数中指定事件对象句柄,从而获得一个复制的句柄,此句柄可以被其它进程使用。 一个进程可以在OpenEvent或CreateEvent函数中指定一个名字,从而获得一个有名的事件对象句柄。 使用CloseHandle函数关闭句柄。当进程停止时,系统将自动关闭句柄。当最后一个句柄被关闭后,事件对象将被销毁。使用环境: Windows NT/2000:需要3.1或更高版本 Windows 95/98:需要Windows 95或更高版本 头文件:定义在Winbase.h;需要包含 Windows.h。 导入库:user32.lib Unicode:在Windows NT/2000中,以 Unicode 和 ANSI 执行 一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle() 来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号,用ResetEvent() 来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待 其变为有信号. PulseEvent()是一个比较有意思的使用方法,正如这个API的名字,它使一个Event 对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的. 对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),而对于 人工复位的Event对象,它释放所有等待的thread.2. WaitForSingleObject的用法WaitForSingleObject的用法DWORD WINAPI WaitForSingleObject(_in HANDLE hHandle,/ 句柄_in DWORD dwMilliseconds/ 时间间隔);hHandlein对象句柄。可以指定一系列的对象,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。dwMillisecondsin定时时间间隔,单位为milliseconds(毫秒).如果指定一个非零值,函数处于等待状态直到hHandle标记的对象被触发,或者时间到了。如果dwMilliseconds为0,对象没有被触发信号,函数不会进入一个等待状态,它总是立即返回。如果dwMilliseconds为INFINITE,对象被触发信号后,函数才会返回。执行成功,返回值指示出引发函数返回的事件。它可能为以下值:WAIT_ABANDONED0x00000080LThe specified object is a mutex object that was not released by the thread that owned the mutex object before the owning thread terminated. Ownership of the mutex object is granted to the calling thread and the mutex state is set to nonsignaled.If the mutex was protecting persistent state information, you should check it for consistency.WAIT_OBJECT_00x00000000LThe state of the specified object is signaled.WAIT_TIMEOUT0x00000102LThe time-out interval elapsed, and the objects state is nonsignaled.WAIT_FAILED(DWORD)0xFFFFFFFFThe function has failed. To get extended error information, callGetLastError.WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。WAIT_OBJECT_0 0x00000000 :指定的对象出有有信号状态WAIT_TIMEOUT 0x00000102:等待超时WAIT_FAILED 0xFFFFFFFF :出现错误,可通过GetLastError得到错误代码在这里举个例子:先创建一个全局Event对象g_event:CEvent g_event;在程序中可以通过调用CEvent:SetEvent设置事件为有信号状态。下面是一个线程函数MyThreadProc()UINT CFlushDlg:MyThreadProc( LPVOID pParam ) WaitForSingleObject(g_event,INFINITE); For(;) . return 0; 在这个线程函数中只有设置g_event为有信号状态时才执行下面的for循环,因为g_event是全局变量,所以我们可以在别的线程中通过g_event. SetEvent控制这个线程。还有一种用法就是我们可以通过WaitForSingleObject函数来间隔的执行一个线程函数的函数体UINT CFlushDlg:MyThreadProc( LPVOID pParam ) while(WaitForSingleObject(g_event,MT_INTERVAL)!=WAIT_OBJECT_0) / return 0;在这个线程函数中可以通过设置MT_INTERVAL来控制这个线程的函数体多久执行一次,当事件为无信号状态时函数体隔MT_INTERVAL执行一次,当设置事件为有信号状态时,线程就执行完毕了。/安全终止MFC线程终止线程有两种情况可以使线程结束:控制函数结束或者根本就不允许线程完成,而提前终止它。我们可以想象在WORD中进行后台打印,如果打印结束了,那线程就可以结束了。如果用户中止了打印,那后台打印线程也要终止了。本文将主要介绍对这两种情况的实现,并且介绍如何获得线程的结束代码。1.对于工作线程,结束它是比较容易的:退出线程函数然后返回一个结束原因的代码就是了。用户可以使用AfxEndThread函数或直接利用return返回。通常0代表成功返回,这不是硬性规定,一切要取决于你了。对于用户界面线程,调用:PostQuitMessage,它所要的唯一的参数就是返回代码,也就是工作线程中的那个码,性质是一样的。0通常代表成功。2.提前终止一个线程也不难:在线程函数中调用AfxEndThread就是了,其中要传入的参数就是返回代码。这会停止线程的执行,释放线程栈,及与线程相关的DLL,并从内存中删除线程对象。AfxEndThread必须在线程函数内调用,如果用户希望从一个线程结束另一个线程,则需要在两个线程间建立通信机制。如果需要获得线程返回代码,只需要调用:GetExitCodeThread就可以了。这个函数的具体作用就看大家具体去查帮助了。它传入的是线程的句柄,和一个提向返回代码的指针。将来就从那个指针得到返回代码。如果线程仍然处于活动状态,那么:GetExitCodeThread得到的返回代码为STILL_ACTIVE,如果已经退出则得到的是返回代码的地址。获得CWinThread对象的返回代码还需要一点麻烦,通常,当CWinThread线程结束时,线程对象就删除了,因为这个对象不存在了,也就没有办法访问对象的m_hThread变量了,为了避免这种情况,可以有两种方法:将m_bAutoDelete设置为FALSE,这使得线程结束后CWinThread对象仍然存在,这样用户就可以访问m_hThread了,但是如果用户使用这种方法,用户需要自己析构CWinThread对象。这种方法是推荐的方法。下一个方法是另外保存线程的句柄。在线程创建后,将m_hThread保存在另一个变量中,以后访问这个变量就是了。但是要小心,在复制句柄以前线程并没有结束,最安全的方法是在AfxBeginThread中传入CREATE_SUSPENDED,保存句柄,然后通过调用ResumeThread,重新开始线程。这两种方法都可以帮助用户得到CWinThread对象的返回代码。对于Worker线程,终止线程可以使用线程的退出码作为返回值从线程函数返回。对于UI线程,因为有消息循环,需要发送一个WM_QUIT消息到线程的消息队列,当线程接收到WM_QUIT消息时退出消息循环。因此,结束线程可以在线程内部调用SDK的PostQuitMessage函数,发送WM_QUIT消息。PostQuitMessage函数的定义如下:void PostQuitMessage(int nExitCode);其中:nExitCode:线程的退出码。MFC还提供了AfxEndThread函数,Worker线程和UI线程都可以通过在线程内部调用AfxEndThread函数结束线程。AfxEndThread函数的定义如下:void AfxEndThread(UINT nExitCode, BOOL bDelete = TRUE);其中:nExitCode:线程的退出码。在MFC的THRDCORE.CPP中,AfxEndThread函数的相关代码如下:/ THRDCORE.CPPvoid AFXAPI AfxEndThread(UINT nExitCode, BOOL bDelete) / remove current CWinThread object from memory AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState(); CWinThread* pThread = pState-m_pCurrentWinThread; if (pThread != NULL) ASSERT_VALID(pThread); ASSERT(pThread != AfxGetApp(); / cleanup OLE if required if (pThread-m_lpfnOleTermOrFreeLib != NULL) (*pThread-m_lpfnOleTermOrFreeLib)(TRUE, FALSE); if (bDelete) pThread-Delete(); pState-m_pCurrentWinThread = NULL; / allow cleanup of any thread local objects AfxTermThread(); / allow C-runtime to cleanup, and exit the thread _endthreadex(nExitCode);从MFC代码中可以看出,AfxEndThread函数通过调用_endthreadex函数终止线程。此外,函数还进行释放线程的堆栈、删除线程对象等工作。如果在其它线程中终止该线程,必须采用线程通信的方法实现。其中一种简单的方法是建立一个变量,让线程监视该变量,当该变量为某个值时,则终止线程。(1)创建1个基于对话框的应用程序,名称为Demo。(2)在IDD_DEMO_DIALOG对话框资源中添加控件,如表所示。类型ID标题StaticIDC_STATIC数据:EditIDC_DATAButtonIDC_BEGIN_THREAD启动线程ButtonIDC_END_THREAD终止线程(3)在文件中定义线程传递参数的数据结构,代码如下:/ DemoDlg.htypedef struct THREAD_PARAM HWND hWnd; int nData; BOOL bExit;_THREAD_PARAM;(4)在CDemoDlg类中添加成员变量,代码如下:/ DemoDlg.hprotected: CWinThread* m_pThread; THREAD_PARAM m_ThreadParam;(5)在CDemoDlg类的构造函数中初始化成员变量,代码如下:/ DemoDlg.cppCDemoDlg:CDemoDlg(CWnd* pParent /*=NULL*/): CDialog(CDemoDlg:IDD, pParent) / . m_pThread = NULL; m_ThreadParam.nData = 0;(6)在CDemoDlg类的OnInitDialog函数中添加如下代码:/ DemoDlg.cpp BOOL CDemoDlg:OnInitDialog() CDialog:OnInitDialog(); / SetDlgItemInt(IDC_DATA, m_nData); return TRUE;(7)在文件中定义线程消息,代码如下:/ DemoDlg.h#define WM_THREADMSG WM_USER+1(8)在文件中定义线程函数,代码如下:/ DemoDlg.hUINT ThreadProc(LPVOID pParam);/ DemoDlg.cppUINT ThreadProc(LPVOID pParam) /线程参数 THREAD_PARAM* pThreadParam = (THREAD_PARAM*)pParam; while (!pThreadParam-bExit) Sleep(100); pThreadParam-nData+; /向主线程窗口发送消息 :PostMessage(pThreadParam-hWnd, WM_THREADMSG, 0, 0); return 0;(9)在CDemoDlg类中分别为Button控件添加BN_CLICKED添加消息处理函数,代码如下:/ DemoDlg.cppvoid CDemoDlg:OnBeginThread() if (m_pThread != NULL) AfxMessageBox(_T(线程已经启动。); return; m_ThreadParam.hWnd = m_hWnd; m_ThreadParam.bExit = FALSE; /启动线程,初始为挂起状态 m_pThread = AfxBeginThread(ThreadProc, &m_ThreadParam, THREAD_PRIORITY_ABOVE_NORMAL, 0, CREATE_SUSPENDED); /线程结束时不自动删除 m_pThread-m_bAutoDelete = FALSE; /恢复线程运行 m_pThread-ResumeThread();void CDemoDlg:OnEndThread() if (m_pThread = NULL) AfxMessageBox(_T(线程已经终止。); return; m_ThreadParam.bExit = TRUE; /等待线程结束 :WaitForSingleObject(m_pThread-m_hThread, INFINITE); delete m_pThread; m_pThread = NULL;(10)在CDemoDlg类中添加自定义消息处理函数,代码如下:/ DemoDlg.hafx_msg LRESULT OnMsgFunc();/ DemoDlg.cppBEGIN_MESSAGE_MAP(CDemoDlg, CDialog) ON_MESSAGE(WM_THREADMSG, OnMsgFunc)END_MESSAGE_MAP()LRESULT CDemoDlg:OnMsgFunc() SetDlgItemInt(IDC_DATA, m_ThreadParam.nData); return 1;/指针在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。1在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。作个比喻,假设将电脑存储器当成一本书,一张内容记录了某个页码加上行号的便利贴,可以被当成是一个指向特定页面的指针;根据便利粘贴面的页码与行号,翻到那个页面,把那个页面的那一行文字读出来,就相当于是对这个指针进行反参考的动作。2在信息工程中指针是一个用来指示一个内存地址的计算机语言的变量或中央处理器(CPU)中寄存器(Register)【用来指向该内存地址所对应的变量或数组】。指针一般出现在比较接近机器语言的语言,如汇编语言或C语言。面向对象的语言如Java一般避免用指针。指针一般指向一个函数或一个变量。在使用一个指针时,一个程序既可以直接使用这个指针所储存的内存地址,又可以使用这个地址里储存的函数的值。另外,指针也指钟表中用来指示对应时间的部件。中文名指针外文名pointer类别指示测量的数据的装置适用范围计算机作用通过它找到以它为地址的内存单元目录1. 1简介2. 2信息工程3. 3按值传递4. 4*和&运算1. 5另类*和&2. 6双级指针3. 7指针的初始化4. 8与数组关系1. 9与“引用”的区别2. 10其他简介使用指针来读取数据,在重复性操作的状况下,可以明显改善程序性能,例如在遍历字符串,查取表格,控制表格及树状结构上。对指针进行复制,之后再解引用指针以取出数据,无论在时间或空间上,都比直接复制及访问数据本身来的经济快速。2指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用(reference)数据形别。许多编程语言中都支持某种形式的指针,最著名的是C语言,但是有些编程语言对指针的运用采取比较严格的限制,如Java一般避免用指针,改为使用引用。2有两种含义,一是作为数据类型,二是作为实体。2指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量。指针一般出现在比较底层的程序设计语言中,如C语言。高层的语言如Java一般避免用指针,而是引用。2指针作为数据类型,可以从一个函数类型、一个对象类型或者一个不完备类型中导出。从中导出的数据类型称之为被引用类型(referenced type)。指针类型描述了一种对象,其值为对被引用类型的实体的引用。2C+标准中规定,“指针”概念不适用于成员指针(不包含指向静态成员的指针)。C+标准规定,指针分为两类:2 object pointer type:指向void或对象类型,表示对象在内存中的字节地址或空指针。2 function pointer type:指代一

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论