为什么windows要使用消息机制.

本文中,子窗口和子控件是一个概念.


对于很多解释消息机制的文章来说,他们的重点是解释是怎样实现的?
而本文的重点是:为什么要用消息机制?为什么不直接使用函数调用,多简单,而要使用复杂的消息机制?




windows系统使用消息机制来实现窗口间的交互...但其实直接使用函数调用也可用达到目的,例如:
鼠标点击时,调用子控件的点击处理函数,
按钮按下的时候,调用按钮按下的函数.
或者直接调用子控件的一个公共的消息处理函数,并传递消息类型和消息数据给该函数.
子控件可以在自己的公共消息处理函数里根据不同的消息类型, 指派不同的函数来处理消息数据.


这样有什么缺点呢?缺点就是父窗口无论怎样都要等待子控件把消息处理完毕才能返回.


那么在单线程程序中,主窗口向子窗口或者兄弟窗口派发消息时,是等消息处理完毕线程才返回呢?还是立刻就返回了的?
答案是通常情况都是发送完消息就立刻返回,例如postmessage()函数,此函数将消息发送的消息队列队尾,
也可以等待消息执行完毕才返回,例如sendmessage()函数,
发送消息后程序立刻就返回了,那就是说,windows在一个单线程程序中实现了多线程的才能做到的事,他怎么做到的?


这利用的是:"单线程异步"实现的,原理是什么呢?原理类似于单核心的CPU电脑实现多任务多进程系统.
每个进程中,都维护着一个消息队列,其中的消息分属于主窗口和主窗口的兄弟窗口,和子窗口等.
进程在消息循环体中,每循环一次就从消息队列中取出一个消息处理,根据消息所属的窗口来调用
所属窗口的消息处理函数来处理不同消息.这样就实现了多窗口间的交互和异步.也就是说单线程中
消息队列是唯一的,但每个窗口有自己的消息处理函数,流程如下:


程序启动
设置窗口各种参数和属性

创建主窗口

系统创建消息队列

显示窗口
开始消息循环
{
        
}

注:    这里特别说明一下,消息队列是系统维护的.而不是进程自己维护.因为如果是进程自己维护,那么当进程繁忙时
(消息处理函数正在运行时),进程是无法去维护消息队列的,这和没用消息队列是一回事.



显示窗口()这个函数会向消息队列发送一个窗口重绘消息,开始消息循环的时候,根据重绘消息,
线程调用主窗口的重绘函数取得桌面DC,在桌面DC上绘制出窗口的图像.
假如主窗口上有个按钮控件,在用户点击按钮后,按钮点击函数需要耗时2秒.
我们希望主窗口程序在触发按钮点击后能立即返回,这是能实现的,原理如下:
点击按钮的时候,主窗口首先收到的是鼠标点击事件,主窗口判断控件所占的区域
是否包含了鼠标坐标,如果包括,创建一个鼠标点击消息,所属为按钮,
坐标为按钮的相对坐标,发送此消息到消息队列尾部.然后程序返回,
剩下的交由消息队列循环自行处理.


虽然是异步,但是当消息循环处理到按钮的鼠标点击消息时,因为耗时2秒,线程会在处理该消息时呆滞2秒.....
因为毕竟还是单线程.


操作系统得到的各种鼠标消息,键盘消息,网络消息,其他消息,还有程序自己的某些函数调用,例如最大化,最小化,等函数,
其他进程发送的消息等.
都会根据一定条件发送到进程的消息队列中.


消息中的消息就是一个个小小的任务,这些任务属于不同的对象不同的窗口,消息循环逐个完成这些任务.
也就把线程的资源分配给了不同的窗口(实例)对象.


其实,就算是我们不需要单线程异步处理流程,我们也必须使用消息队列,因为没有消息队列,
其他进程就无法向改程序发送消息了..
(没有消息队列,其他进程对进程互动呢?我们可以让进程公开事件处理函数,其他进程调用该函数传入不同事件,
但是,假如进程在处理某个事件没处理完毕,这个进程就被系统死锁住,其他进程就无法调用该进程的公共进程处理函数.
导致其他进程等待.)
没有消息队列,同进程中的线程间也无法交互,操作系统也无法向进程发送消息.(并非无法交互,而是会导致很多进程都处于等待状态)


所以必须要使用消息队列,让系统与进程间,进程间与进程间,线程与线程间,发送消息之后(也就是在消息队列中添加一个消息后)
,立刻返回.而不是去调用进程的函数.


另外在一个线程之中,只能维护一个消息队列,证明如下:
假如每个窗口都自己维护一个消息队列,那怎么必须由主消息队列循环中的某个函数来调用子窗口的消息循环,例如在子窗口创建
时,做一个死循环,如下:
while(true)
{
        如果消息队列数不为0
        {
            处理队首消息;
            去除消息队列队首消息;
        }
}
问题是单线程程序中,程序会在子窗口构造执行到这个死循环的时候,一直死在这里,无法从循环中退出来.而如果像下面这样:
while(true)
{
        如果消息队列数不为0
        {
            处理队首消息;
            去除消息队列队首消息;
        }
        否则退出循环;
}
这样的代码又会导致这个消息循环在刚刚建立时就立刻退出了.并且以后永远也不会执行.
所以,单线程是无法实现维护多个消息队列的.


我们可以用多线程维护多个消息队列,每个窗口创建一个线程,维护一个消息队列.
但是问题是:线程对硬件资源的开销较大,如果一个程序有很多窗口的时候,有较大的性能损失.
所以,最后的最后,微软选择了单线程消息队列机制(异步处理机制)
但现在仍然还有一个问题,那就是:当进程中的消息全部处理完毕时,怎样释放进程占用的CPU资源?
其实这个也简单,我自己想来这样处理,
如果消息队列没有消息,就sleep(无限期);无限期释放资源,
那当有消息时怎么办?
因为是无限期挂起,所以,进程不会自己主动恢复,
所以,在发送消息时,我们同时要让进程恢复运行状态.




下面是本文中可能会用到的一些知识:


阻塞队列,
队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具。工作者线程可以定期地把中间结果存到阻塞队列中而其他工作者线线程把中间结果取出并在将来修改它们。队列会自动平衡负载。如果第一个线程集运行得比第二个慢,则第二个线程集在等待结果时就会阻塞。如果第一个线程集运行得快,那么它将等待第二个线程集赶上来。下表显示了jdk1.5中的阻塞队列的操作:
 
add        增加一个元索                     如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove   移除并返回队列头部的元素    如果队列为空,则抛出一个NoSuchElementException异常
element  返回队列头部的元素             如果队列为空,则抛出一个NoSuchElementException异常
offer       添加一个元素并返回true       如果队列已满,则返回false
poll         移除并返问队列头部的元素    如果队列为空,则返回null
peek       返回队列头部的元素             如果队列为空,则返回null
put         添加一个元素                      如果队列满,则阻塞
take        移除并返回队列头部的元素     如果队列为空,则阻塞
 
可能会用到这个函数
WaitForSingleObject 


有两个参数,
句柄,hHandle
时间dwMilliseconds
WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。




猜你喜欢

转载自blog.csdn.net/qq_33534723/article/details/50415490