Winsows消息处理和多线程编程

来源:互联网 发布:网络夸人的顺口溜 编辑:程序博客网 时间:2024/05/31 19:36

一)Winsows消息处理

1,单线程:程序代码只有一条执行路径。

2,单线程程序消息处理过程:
MSG message;
while(::GetMessage(&message,NULL,0,0))
{
::TranslateMessage(&message);//翻译如wm_char消息
::DispatchMessage(&message);//把消息分发给指定窗口的回调函数
}
说明:
1)os决定哪个消息属于我们的程序,当一个消息要处理的时候,用GetMessage函数返回该消息。
2)如果没有属于我们程序的消息发出,则我们的程序被挂起,而其它的程序可以运行;当属于我们程序的消息到达,我们的程序被唤醒

3,改造2中的单线程消息处理过程:
问题一:某个消息控制函数很“笨重”,消耗CPU时间过久。
问题二:DispatchMessage函数要等到该笨重消息控制函数返回后才能返回。在该笨重消息控制函数返回之前DispatchMessage不处理(分发)任何消息。
解决方法:使用PeekMessage函数使占用CPU时间过久的消息控制函数每隔一段时间交出控制一次。(在占CPU时间过久的控制函数中利用PostMessage发送
代码如下:
MSG message;
while(::PeekMessage(&message,NULL,0,0,PM_REMOVE))
{
::TranslateMessage(&message);//翻译如wm_char消息
::DispatchMessage(&message);//把消息分发给指定窗口的回调函数
}
说明:
//The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
简要说明:
1)BOOL PeekMessage(
  LPMSG lpMsg,         // pointer to structure for message
  HWND hWnd,           // handle to window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax,  // last message
  UINT wRemoveMsg      // removal flags
);
//If a message is available, return nonzero,otherwise return 0;
//Unlike the GetMessage function, the PeekMessage function does not wait for a message to be placed in the queue before returning.
//PeekMessage retrieves only messages associated with the window identified by the hWnd parameter or any of its children as specified by the IsChild function, and within the range of message values given by the wMsgFilterMin and wMsgFilterMax parameters.
//If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread making the call.
//If hWnd is –1, PeekMessage only returns messages with a hWnd value of NULL, as posted by the PostThreadMessage function.
//If wMsgFilterMin and wMsgFilterMax are both zero, PeekMessage returns all available messages (that is, no range filtering is performed).
//PeekMessage does not retrieve messages for windows that belong to other threads.

2)BOOL GetMessage(
  LPMSG lpMsg,         // address of structure with message
  HWND hWnd,           // handle of window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax   // last message
);
//GetMessage does not retrieve messages for windows that belong to other threads or applications.
//

4,计时器独立于CPU时钟速度。
1)计时器的使用:用一个时间间隔参数调用CWnd::SetTimer函数,然后再用ClassWizard为WM_TIMER添加消息控制函数。
2)一旦调用CWnd::SetTimer启动了计时器,则WM_TIMER消息会被持续发送到指定的窗口,直至调用CWnd::KillTimer函数或计时器窗口被取消为止。
3)由于WINDOWS非实时OS,如果时间间隔少于100毫秒,计时器可能不准。
4)OS发送计时器消息的时候,如果队列里已经有了计时器消息,则不会把同样的计时器消息放进消息队列里。
说明:
1)CWnd::SetTimer
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
//Return The timer identifier of the new timer if the function is successful. An application passes this value to the KillTimer member function to kill the timer. Nonzero if successful; otherwise 0.
//nIDEvent:Specifies a nonzero timer identifier.
//nElapse:Specifies the time-out value, in milliseconds.
//lpfnTimer:Specifies the address of the application-supplied TimerProc callback function that processes the WM_TIMER messages. If this parameter is NULL, the WM_TIMER messages are placed in the application’s message queue and handled by the CWnd object.
//CWnd::SetTimer Installs a system timer. A time-out value is specified, and every time a time-out occurs, the system posts aWM_TIMER message to the installing application’s message queue or passes the message to an application-defined TimerProc callback function.
2)CWnd::KillTimer
BOOL KillTimer( int nIDEvent );
//Return value:Return nozero if the event is killed.It is 0 if the KillTimer member function could not find the specified timer event.
//CWnd::KillTimer Kills the timer event identified by nIDEvent from the earlier call to SetTimer. Any pending WM_TIMER messages associated with the timer are removed from the message queue.


5,在引入多线程编程之前,WINDOWS程序员曾使用空状态处理来完成一些后台任务。(这里是OnIdle函数)
1)应用程序框架在框架消息处理循环中调用CWinApp::OnIdle(虚函数),可以重载它来处理后台任务。通常,一但OnIdle函数完成了它的工作,就要等到下次应用程序消息队列空了后才被调用。基类的OnIdle会更新工具栏按钮和状态栏指示器,并清除各种临时对象指针。
2)重载OnIdle函数可以用来更新用户界面,很有实际意义。(注:在重载OnIdle函数中一定要调用基类的OnIdle函数哦)
3)如果用户运行在一个模式对话框或正在使用菜单,则这时OnIdle函数不被调用。但我们可以在框架类中添加WM_ENTERIDLE消息控制函数来在上叙情况下使用后台处理。(注意,这里是在框架类中添加,不是在视图类中。弹出式对话框中是属于应用程序主框架窗口。)

///////
///////

二,多线程编程:

1,一些概念:
1)一个进程就是一个运行的程序,进程具有独立的内存、文件句柄和其它系统资源。
2)一个独立的进程可以包含多条执行路径,称为线程。线程有OS管理,每个线程有自己的堆栈。
3)大多数情况下,进程的所有代码和数据空间被进程内所有的线程所共享。
4)Windows提供两中线程:辅助线程 和 用户界面线程。mfc对两种线程都支持。
5)一个用户界面线程有窗口,因而有自己的消息循环;辅助线程没有窗口,因而不需要消息处理。

2,启动辅助线程:
准备工作:写一个全局函数。格式:UINT MyControllingFunction( LPVOID pParam );
启动辅助线程:使用AfxBeginThread函数为全局函数MyControllingFunction创建线程。
说明一:
MFC 通过参数重载提供两个版本的 AfxBeginThread:一个用于用户界面线程,另一个用于辅助线程。
//用户界面线程通常用于处理用户输入和响应用户事件,这些行为独立于执行该应用程序其他部分的线程。已经创建并启动主应用程序线程(在 CWinApp 导出的类中提供)。
//辅助线程通常用于处理后台任务,用户不必等待就可以继续使用应用程序。重新计算和后台打印等任务是很好的辅助线程示例。
说明二:
创建用户界面线程比创建辅助复杂(需要重写一些相关的函数),这里只笔记创建辅助线程一些笔记。
1)AfxBeginThread
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
//Return Value:Pointer to the newly created thread object.
//pfnThreadProc:Points to the controlling function for the worker thread. Cannot be NULL. This function must be declared as follows:    UINT MyControllingFunction( LPVOID pParam );
//pParam:Parameter to be passed to the controlling function as shown in the parameter to the function declaration in pfnThreadProc.
//nPriority:The desired priority(优先权) of the thread. If 0, the same priority as the creating thread will be used. For a full list and description of the available priorities, seeSetThreadPriority in the Win32 Programmer’s Reference.
//nStackSize:Specifies the size in bytes of the stack for the new thread. If 0, the stack size defaults to the same size stack as the creating thread.
//dwCreateFlags:Specifies an additional flag that controls the creation of the thread. This flag can contain one of two values:
[CREATE_SUSPENDED]   Start the thread with a suspend count of one. The thread will not execute until ResumeThread is called.
[ 0  ]  Start the thread immediately after creation.
//lpSecurityAttrs:Points to a SECURITY_ATTRIBUTES structure that specifies the security attributes for the thread. If NULL, the same security attributes as the creating thread will be used.
//Remarks:
Call this function to create a new thread. The first form of AfxBeginThread creates a worker thread. The second form creates a user-interface thread.
To end the thread, call AfxEndThread from within the thread, or return from the controlling function of the worker thread.
2)挂起线程:
CWinThread::SuspendThread
DWORD SuspendThread( );
//Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise.
//CWinThread::SuspendThread:Increments the current thread’s suspend(悬挂,延缓) count. If any thread has a suspend count above zero, that thread does not execute.
3)恢复线程运行:
CWinThread::ResumeThread
DWORD ResumeThread( );
//Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise. If the return value is zero, the current thread was not suspended. If the return value is one, the thread was suspended, but is now restarted. Any return value greater than one means the thread remains suspended.
//Remarks:Called to resume(恢复) execution of a thread that was suspended(暂停,延缓) by the SuspendThread member function, or a thread created with the CREATE_SUSPENDED flag. The suspend count of the current thread is reduced by one. If the suspend count is reduced to zero, the thread resumes execution; otherwise the thread remains suspended.
4)创建辅助线程事例:
UINT MyThreadProc( LPVOID pParam )
{
    CMyObject* pObject = (CMyObject*)pParam;
    if (pObject == NULL ||
        !pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
    return 1;   // if pObject is not valid
    // do something with 'pObject'
    return 0;   // thread completed successfully
}
// inside a different function in the program
...
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);

3,主线程 和 辅助线程 的通话:(这里的主线程指应用程序,是个用户界面线程)
1)最简单的方法:使用全局变量。
(注意:如书上事例B中在一个辅助线程中使用全局的计数器,不希望其它现在在计数器递增的时候由于其它线程访问而引起混乱,则将起声明成volatile变量保证计数器不被保存到寄存器中,也可以使用InterlockedIncrement来阻塞其它线程同时使计数器递增。)
补充一:
InterlockedIncrement
LONG InterlockedIncrement(
  LPLONG lpAddend   // pointer to the variable to increment
);
//The InterlockedIncrement function both increments (increases by one) the value of the specified 32-bit variable and checks the resulting value. The function prevents more than one thread from using the same variable simultaneously.
补充二:
volatile
使用 volatile 修饰符能够确保一个线程检索由另一线程写入的最新值。
//当字段声明中含有 volatile 修饰符时,该声明引入的字段为易失字段。

由于采用了优化技术(它会重新安排指令的执行顺序),在多线程的程序运行环境下,如果不采取同步控制手段,则对于非易失字段的访问可能会导致意外的和不可预见的结果。这些优化可以由编译器、运行时系统或硬件执行。但是,对于易失字段,优化时的这种重新排序必须遵循以下规则:

读取一个易失字段称为易失读取。易失读取具有“获取语义”;也就是说,按照指令序列,所有排在易失读取之后的对内存的引用,在执行时也一定排在它的后面。
写入一个易失字段称为易失写入。易失写入具有“释放语义”;也就是说,按照指令序列,所有排在易失写入之前的对内存的引用,在执行时也一定排在它的前面。
这些限制能确保所有线程都会观察到由其他任何线程所执行的易失写入(按照原来安排的顺序)。一个遵循本规范的实现并非必须做到:使易失写入的执行顺序,在所有正在执行的线程看来都是一样的。易失字段的类型必须是下列类型中的一种:
引用类型。

类型 byte、sbyte、short、ushort、int、uint、char、float 或 bool。
枚举基类型为 byte、sbyte、short、ushort、int 或 uint 的枚举类型。
(China msdn-C# 语言规范 )

2)不能使用Windows消息(即不能主线程向辅助线程发送消息通信),辅助线程没有窗口没有消息循环。


4,辅助线程 和 主线程(用户界面线程) 通信
1)辅助线程向主线程(用户界面线程)发送Windows消息,由主线程响应该消息,从而实现通信。(主线程有一个窗口,可见或不可见,如果辅助线程可以得到主线程的窗口句柄,便可以向主线程发送Windows消息了。主线程总是有一个消息循环的。)
2)辅助线程可以通过AfxBeginThread函数参数传入主线程句柄从而得到主线程的句柄。
3)辅助线程使用寄出(post)消息。任何用户定义的消息都可以。(使用送出(SEND)消息会引起主线程MFC消息处理代码的重入,这在模式对话框中会出现问题。)


5,SDI事例说明:
1)CComputeDlg::OnStart函数中,利用AfxBeginThread(ComputeThreadProc, GetSafeHwnd(),THREAD_PRIORITY_NORMAL);函数为用户自定义的全局函数ComputeThreadProc创建辅助线程的同时,利用GetSafeHwnd()获得对话框句柄并做为参数传入
ComputeThreadProc函数形参pParam中。
//GetSafeHwnd() Returns the window handle for a window. Returns NULL if the CWnd is not attached to a window or if it is used with a NULL CWnd pointer.
2)UINT ComputeThreadProc(LPVOID pParam)函数中利用传进来的参数pParam,调用::PostMessage((HWND) pParam, WM_THREADFINISHED, 0, 0)函数向对话框窗口发送消息用户自定义WM_THREADFINISHED消息。
3)在对话框类中为WM_THREADFINISHED添加控制函数。
三步:
消息控制函数声明:CComputeDlg类头文件 LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);消息映射:CComputeDlg类代码文件 ON_MESSAGE(WM_THREADFINISHED, OnThreadFinished)
消息控制函数:CComputeDlg类代码文件
LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)
{
 CDialog::OnOK();
 return 0;
}

6,排斥区(CCriticalSection)
MFC提供了CCriticalSection类来帮助我们实现在线程之间共享全局数据(保证对其临界访问).
使用方法下面代码演示:
CCriticalSection g_cs;//定义g_cs为临界访问对象
int g_nCount;
voit func()
{
 g_cs.Lock();
 g_nCount++;
 g_cs.Unlock();
}
说明:
1)CCriticalSection从CSyncObject类派生而来:
An object of class, CCriticalSection represents a "critical section" - a synchronization object that allows one thread at a time to access a resource or section of code. Critical sections are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
2)构造函数CCriticalSection( )说明:
Constructs a CCriticalSection object. To access or release a CCriticalSection object, create a CSingleLock object and call its Lock and Unlock member functions. If the CCriticalSection object is being used stand-alone, call its Unlock member function to release it.
3)CCriticalSection::Unlock:  Releases the CCriticalSection object.
CCriticalSection::Lock: Use to gain access to the CCriticalSection object.
4)进一步使用说明:
当线程A正在执行func()函数使g_nCount++增1的时候,线程B调用func()函数执行到g_cs.Lock()的时候线程B被阻塞直到线程A执行了g_cs.Unlock()才继续往下执行g_nCount++。
5)CCriticalSection只是用于当个进程内的控制访问,如果要在不同的进程之间控制数据的访问需要使用 互斥体(CMutex) 和 信号(semaphore)。


7,互斥体(CMutex)
1)CMutex:
An object of class CMutex represents a “mutex” — a synchronization object that allows one thread mutually exclusive access to a resource. Mutexes are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
2)CMutex类从CSyncObject类派生而来,其一般用法参见下E文:
To use a CMutex object, construct the CMutex object when it is needed. Specify the name of the mutex you wish to wait on, and that your application should initially own it. You can then access the mutex when the constructor returns. Call CSyncObject::Unlock when you are done accessing the controlled resource.


8,信号(CSemaphore)
1)CSemaphore(也是从CSyncObject类派生而来):
An object of class CSemaphore represents a “semaphore” — a synchronization object that allows a limited number of threads in one or more processes to access a resource. A CSemaphore object maintains a count of the number of threads currently accessing a specified resource.
2)Semaphores are useful in controlling access to a shared resource that can only support a limited number of users.The current count of the CSemaphore object is the number of additional users allowed. When the count reaches zero, all attempts to use the resource controlled by the CSemaphore object will be inserted into a system queue and wait until they either time out or the count rises above 0. The maximum number of users who can access the controlled resource at one time is specified during construction of the CSemaphore object.
构造函数:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

9,关于从CSyncObject类派生类一些说明:
1)CCriticalSection,CMutex,CSemaphore,CEvent,derived from CSyncObject。
2)CSyncObject类成员中包含一下两成员函数:
Lock   Gains access to the synchronization object.
Unlock   Releases access to the synchronization object.