原文:
Windows程序是基于消息的,不管其封装形式如何,最后都要包含如下代码
-
MSGmsg;
-
while(GetMesssage(&msg))
-
{
-
TranslateMessage(&msg);
-
DispatchMessage(&msg);
-
}
大部分的工作都是在这个while循环里完成。 GetMessage获得一条消息,然后调用DispatchMessage分发消息。DispatchMessage首先找到消息对应的窗口,调用窗口的消息处理函数,在消息处理函数里通过switch/case执行对应的处理代码,然后返回继续获得下一个消息GetMessage。直到收到WM_QUIT消息,while循环才会退出,否则一直阻塞在GetMessage处等待消息。一条线程启动后,如果不是进入消息循环,那么很快就结束,比如以下线程入口函数
-
unsignedintWINAPIThreadFunc(LPVOIDparam)
-
{
-
...
-
-
-
-
}
因为创建线程是有代价的,我们希望线程能一直运行,所以加入消息循环,比如
-
unsignedintWINAPIThreadFunc(LPVOIDparam)
-
{
-
...
-
-
-
while(WaitForMessage())
-
{
-
DispatchMessage();
-
}
-
-
}
消息循环就是让线程一直运行在类似的while循环中,不断检查和分发消息,直到收到特定消息而退出循环,然后线程就结束了。一般来说,除非只为特定工作而创建的线程之外,通用的公共线程都应该有一个消息队列和消息循环。 这样,我们可以通过把消息发送到指定线程的消息队列中,从而让消息在指定线程中处理,而避免多线程访问所带来的并发访问问题。在Chrome里,是使用MessageLoop对象来封装以上的消息处理循环代码。一条线程在启动后,就会创建MessageLoop对象,并调用MessageLoop.Run()方法来进入消息循环。当MessageLoop.Run()函数返回,即线程退出。
-
classMessageLoop:publicbase::MessagePump::Delegate{
-
{
-
public:
-
-
-
-
voidRun();
-
-
-
-
-
-
-
-
-
-
-
-
voidQuit();
-
-
-
-
staticMessageLoop*current();
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
voidPostTask(consttracked_objects::Location&from_here,Task*task);
-
}
调用MessageLoop::current()方法可以获得当前线程对应的MessageLoop对象,并可以调用其PostTask方法让该线程上执行一个Task。PostTask把Task放入队列后就返回了,Task会在稍后的消息循环中得到处理。MessageLoop除了执行Task,还可以处理常规的windows消息以及IO事件等,具体要看MessagLoop的类型。 MessageLoop内部使用使用MessagePump对象来处理消息,并根据不同的类型创建不同的MessagePump。
-
MessageLoop::MessageLoop(Typetype)
-
{
-
lazy_tls_ptr.Pointer()->Set(this);
-
-
-
if(type_==TYPE_DEFAULT){
-
pump_=newbase::MessagePumpDefault();
-
}elseif(type_==TYPE_IO){
-
pump_=newbase::MessagePumpForIO();
-
}else{
-
DCHECK(type_==TYPE_UI);
-
pump_=newbase::MessagePumpForUI();
-
}
-
}
-
-
-
voidMessageLoop::RunInternal()
-
{
-
pump_->Run(this);
-
}
MessagePumpDefault最简单,只执行Task。MessagePumpForUI可以处理windows消息队列,而MessagePumpForIO可以处理IO完成端口的消息。不同Pump获得消息的方式是不一样的,MessagePumpDefault等待Event信号量,PumpForUI从Windows的消息队列,而PumpForIO是从IO完成端口。
-
voidMessagePumpDefault::Run(Delegate*delegate){
-
-
if(delayed_work_time_.is_null()){
-
event_.Wait();
-
}else{
-
TimeDeltadelay=delayed_work_time_-Time::Now();
-
if(delay>TimeDelta()){
-
event_.TimedWait(delay);
-
}else{
-
delayed_work_time_=Time();
-
}
-
}
-
}
-
-
-
voidMessagePumpForUI::WaitForWork(){
-
-
-
DWORDresult;
-
result=MsgWaitForMultipleObjectsEx(0,NULL,delay,QS_ALLINPUT,
-
MWMO_INPUTAVAILABLE);
-
-
-
if(WAIT_OBJECT_0==result){
-
MSGmsg={0};
-
DWORDqueue_status=GetQueueStatus(QS_MOUSE);
-
if(HIWORD(queue_status)&QS_MOUSE&&
-
!PeekMessage(&msg,NULL,WM_MOUSEFIRST,WM_MOUSELAST,PM_NOREMOVE)){
-
WaitMessage();
-
}
-
return;
-
}
-
-
-
-
-
boolMessagePumpForIO::GetIOItem(DWORDtimeout,IOItem*item){
-
memset(item,0,sizeof(*item));
-
ULONG_PTRkey=NULL;
-
OVERLAPPED*overlapped=NULL;
-
if(!GetQueuedCompletionStatus(port_.Get(),&item->bytes_transfered,&key,
-
&overlapped,timeout)){
-
if(!overlapped)
-
returnfalse;
-
item->error=GetLastError();
-
item->bytes_transfered=0;
-
}
-
-
-
item->handler=reinterpret_cast<IOHandler*>(key);
-
item->context=reinterpret_cast<IOContext*>(overlapped);
-
returntrue;
-
}
PostTask只是把Task放在一个Task队列里的,如果这时候线程处于等待消息的阻塞状态,那么还需要发送一条消息去唤醒线程。不同的Pump使用不同的方式,PumpDefault是等待Event信号量,PumpForUI是阻塞在GetMessage上等待Windows消息,所以可以发送Windows消息唤醒。而PumpForIO是阻塞在IO完成端口上,那么只要模拟发送一个IO完成信息过去即可唤醒线程。
-
voidMessagePumpDefault::ScheduleWork(){
-
-
-
event_.Signal();
-
}
-
-
-
voidMessagePumpForUI::ScheduleWork(){
-
PostMessage(message_hwnd_,kMsgHaveWork,reinterpret_cast<WPARAM>(this),0);
-
}
-
-
-
voidMessagePumpForIO::ScheduleWork(){
-
BOOLret=PostQueuedCompletionStatus(port_,0,
-
reinterpret_cast<ULONG_PTR>(this),
-
reinterpret_cast<OVERLAPPED*>(this));
-
}
MessagePump对Native消息(Windows消息或IO完成消息)有自己的处理方式(PumpDefault没有消息要处理),而Task则由MessageLoop来统一处理。
MessageLoop对象从队列里取出Task执行,即调用Task::Run,然后删除。
-
voidMessageLoop::RunTask(Task*task){
-
task->Run();
-
deletetask;
-
}
当一条线程创建了MessageLoop对象之后,它就具备了运行Task的能力,我们就可以任意指派Task在该线程执行。就像这样
-
some_message_loop->PostTask(some_task);
因为MessageLoop的本意是处理和分发消息的,是共用,所以消息处理代码不宜运行过长时间,包括Task。因为这会长时间阻塞住整条MessageLoop,影响其它消息的处理。对于需要长时间运行的,还是需要创建单独的工作线程,或者调用异步执行代码。 比如Socket, Pipe和File都是可以用非阻塞的方式操作,即异步IO--在Windows平台上可以使用IO完成端口来方便处理。MessageLoopForIO可以用于异步IO专用线程,调用者只有把IO对象的句柄交给MessageLoopForIO对象(MessagePumpForIO
::RegisterIOHandler),以后每当有IO对象的操作完成时,调用者就会从MessageLoopForIO收到回调通知(IOHandler::OnIOCompleted)。 MessageLoopForUI就更不用说了,因为Windows窗口是绑定到创建线程上的,所以只要有一个MessageLoopForUI对象就可以处理属于该线程的所有Windows消息,不需要额外的注册过程。
-
classMessagePumpForIO:publicMessagePumpWin{
-
-
-
classIOHandler{
-
public:
-
virtual~IOHandler(){}
-
-
-
-
-
virtualvoidOnIOCompleted(IOContext*context,DWORDbytes_transfered,
-
DWORDerror)=0;
-
};
-
-
-
-
-
voidRegisterIOHandler(HANDLEfile_handle,IOHandler*handler);
-
-
-
}
所以,如果相关模块都可以异步调用,那么只要有两条线程(分别处理UI和IO消息)即可满足程序的需要,而主线程一般就是UI线程。 使用基于MessageLoop的消息分发机制,可以大大减少线程的数量,避免程序并发带来的各种问题。
分享到:
相关推荐
chromium message loop 封装使用的一个跨线程通信demo,内含有chromium base库(部分抽取),以及消息循环跨线程通信的一个使用案例。
Chromium OS 技术分析程序全攻略書中程式代碼
Chromium OS 技术分析程序全攻略 final edition
也是网上看到的,android4.4的webview基于chromium,各个类的关系图
我们下面就通过分析代码来一一探明。 chromium 系统有多个平台的实现,而每个平台都有自己不同的事件管理方式,chromium 也会有一些平台相关的代码。本文目前主要是研究 Linux 平台相关的实现,其他平台后续 再做...
chromium命令行参数1
Chromium硬件加速渲染的UI合成过程分析
ungoogled-chromium是Google Chromium,不依赖于Google Web服务。 ungoogled-chromium尽可能保留默认的Chromium体验。与其他具有Web浏览器愿景的其他Chromium分支不同,ungoogled-chromium本质上是Chromium的直接...
chromium-linux.zip
chromium离线安装版49 x86 32位windows xp 2003 亲测好用.chromium 不会默认上传您的用户数据,且性能比同版本chrome强10%。差不多可以说算得上是xp 2003 下最好用的 浏览器了
该文件属于Chromium 58版本的编译配置文档,其中,包含每一项参数的说明。 请注意,该文档是通过gn args --list out/debug生成的,如果你已下载Chromium源码,可以手动生成,不必下载。
SwitchyOmega-Chromium
SwitchyOmega_Chromium插件
LICENSES.chromium
基于chromium源码的智能指针实现分析,探究其底层实现原理
Chromium浏览器官方版是一个由Google主导开发的网页浏览器,Chromium浏览器官方版是Google为发展Google Chrome而开启的计划,所以Chromium浏览器相当于Chrome的工程版或称实验版,新功能会率先在Chromium上实现,待...
SwitchyOmega_Chromium.crx
Chromium离线安装包,适用于树莓派。我的博客中有教程。
Chromium Nightly Updater 1.1
断断续续下载chromium代码花了1周左右,代码约15G左右,其中遇到power shell的问题,遇到python的问题。最终编译花了11:30分。相比于Linux下的编译慢很多,系统运行的稳定性和速度也差很多。打算研究源码,一起分享...