`
hyshucom
  • 浏览: 810906 次
文章分类
社区版块
存档分类
最新评论

Chromium MessageLoop类分析

 
阅读更多

原文:

Windows程序是基于消息的,不管其封装形式如何,最后都要包含如下代码

Cpp代码收藏代码
  1. MSGmsg;
  2. while(GetMesssage(&msg))
  3. {
  4. TranslateMessage(&msg);
  5. DispatchMessage(&msg);
  6. }


大部分的工作都是在这个while循环里完成。 GetMessage获得一条消息,然后调用DispatchMessage分发消息。DispatchMessage首先找到消息对应的窗口,调用窗口的消息处理函数,在消息处理函数里通过switch/case执行对应的处理代码,然后返回继续获得下一个消息GetMessage。直到收到WM_QUIT消息,while循环才会退出,否则一直阻塞在GetMessage处等待消息。


一条线程启动后,如果不是进入消息循环,那么很快就结束,比如以下线程入口函数

Cpp代码收藏代码
  1. unsignedintWINAPIThreadFunc(LPVOIDparam)
  2. {
  3. ...
  4. //执行到这里,线程就准备退出了
  5. }


因为创建线程是有代价的,我们希望线程能一直运行,所以加入消息循环,比如


Cpp代码收藏代码
  1. unsignedintWINAPIThreadFunc(LPVOIDparam)
  2. {
  3. ...
  4. while(WaitForMessage())
  5. {
  6. DispatchMessage();
  7. }
  8. }



消息循环就是让线程一直运行在类似的while循环中,不断检查和分发消息,直到收到特定消息而退出循环,然后线程就结束了。




一般来说,除非只为特定工作而创建的线程之外,通用的公共线程都应该有一个消息队列和消息循环。 这样,我们可以通过把消息发送到指定线程的消息队列中,从而让消息在指定线程中处理,而避免多线程访问所带来的并发访问问题。




在Chrome里,是使用MessageLoop对象来封装以上的消息处理循环代码。一条线程在启动后,就会创建MessageLoop对象,并调用MessageLoop.Run()方法来进入消息循环。当MessageLoop.Run()函数返回,即线程退出。


Cpp代码收藏代码
  1. classMessageLoop:publicbase::MessagePump::Delegate{
  2. {
  3. public:
  4. //Runthemessageloop.
  5. voidRun();
  6. //SignalstheRunmethodtoreturnafteritisdoneprocessingallpending
  7. //messages.Thismethodmayonlybecalledonthesamethreadthatcalled
  8. //Run,andRunmuststillbeonthecallstack.
  9. //
  10. //UseQuitTaskifyouneedtoQuitanotherthread'sMessageLoop,butnote
  11. //thatdoingsoisfairlydangerousifthetargetthreadmakesnestedcalls
  12. //toMessageLoop::Run.Theproblembeingthatyouwon'tknowwhichnested
  13. //runloopyouarequiting,sobecareful!
  14. //
  15. voidQuit();
  16. //ReturnstheMessageLoopobjectforthecurrentthread,ornullifnone.
  17. staticMessageLoop*current();
  18. //The"PostTask"familyofmethodscallthetask'sRunmethodasynchronously
  19. //fromwithinamessageloopatsomepointinthefuture.
  20. //
  21. //WiththePostTaskvariant,tasksareinvokedinFIFOorder,inter-mixed
  22. //withnormalUIorIOeventprocessing.
  23. //
  24. //TheMessageLooptakesownershipoftheTask,anddeletesitafterithas
  25. //beenRun().
  26. //
  27. //NOTE:Thesemethodsmaybecalledonanythread.TheTaskwillbeinvoked
  28. //onthethreadthatexecutesMessageLoop::Run().
  29. voidPostTask(consttracked_objects::Location&from_here,Task*task);
  30. }



调用MessageLoop::current()方法可以获得当前线程对应的MessageLoop对象,并可以调用其PostTask方法让该线程上执行一个Task。PostTask把Task放入队列后就返回了,Task会在稍后的消息循环中得到处理。


MessageLoop除了执行Task,还可以处理常规的windows消息以及IO事件等,具体要看MessagLoop的类型。 MessageLoop内部使用使用MessagePump对象来处理消息,并根据不同的类型创建不同的MessagePump。




Cpp代码收藏代码
  1. MessageLoop::MessageLoop(Typetype)
  2. {
  3. lazy_tls_ptr.Pointer()->Set(this);
  4. if(type_==TYPE_DEFAULT){
  5. pump_=newbase::MessagePumpDefault();
  6. }elseif(type_==TYPE_IO){
  7. pump_=newbase::MessagePumpForIO();
  8. }else{
  9. DCHECK(type_==TYPE_UI);
  10. pump_=newbase::MessagePumpForUI();
  11. }
  12. }
  13. voidMessageLoop::RunInternal()
  14. {
  15. pump_->Run(this);
  16. }



MessagePumpDefault最简单,只执行Task。MessagePumpForUI可以处理windows消息队列,而MessagePumpForIO可以处理IO完成端口的消息。不同Pump获得消息的方式是不一样的,MessagePumpDefault等待Event信号量,PumpForUI从Windows的消息队列,而PumpForIO是从IO完成端口。


Cpp代码收藏代码
  1. voidMessagePumpDefault::Run(Delegate*delegate){
  2. if(delayed_work_time_.is_null()){
  3. event_.Wait();
  4. }else{
  5. TimeDeltadelay=delayed_work_time_-Time::Now();
  6. if(delay>TimeDelta()){
  7. event_.TimedWait(delay);
  8. }else{
  9. delayed_work_time_=Time();
  10. }
  11. }
  12. }
  13. voidMessagePumpForUI::WaitForWork(){
  14. DWORDresult;
  15. result=MsgWaitForMultipleObjectsEx(0,NULL,delay,QS_ALLINPUT,
  16. MWMO_INPUTAVAILABLE);
  17. if(WAIT_OBJECT_0==result){
  18. MSGmsg={0};
  19. DWORDqueue_status=GetQueueStatus(QS_MOUSE);
  20. if(HIWORD(queue_status)&QS_MOUSE&&
  21. !PeekMessage(&msg,NULL,WM_MOUSEFIRST,WM_MOUSELAST,PM_NOREMOVE)){
  22. WaitMessage();
  23. }
  24. return;
  25. }
  26. boolMessagePumpForIO::GetIOItem(DWORDtimeout,IOItem*item){
  27. memset(item,0,sizeof(*item));
  28. ULONG_PTRkey=NULL;
  29. OVERLAPPED*overlapped=NULL;
  30. if(!GetQueuedCompletionStatus(port_.Get(),&item->bytes_transfered,&key,
  31. &overlapped,timeout)){
  32. if(!overlapped)
  33. returnfalse;//Nothinginthequeue.
  34. item->error=GetLastError();
  35. item->bytes_transfered=0;
  36. }
  37. item->handler=reinterpret_cast<IOHandler*>(key);
  38. item->context=reinterpret_cast<IOContext*>(overlapped);
  39. returntrue;
  40. }





PostTask只是把Task放在一个Task队列里的,如果这时候线程处于等待消息的阻塞状态,那么还需要发送一条消息去唤醒线程。不同的Pump使用不同的方式,PumpDefault是等待Event信号量,PumpForUI是阻塞在GetMessage上等待Windows消息,所以可以发送Windows消息唤醒。而PumpForIO是阻塞在IO完成端口上,那么只要模拟发送一个IO完成信息过去即可唤醒线程。


Cpp代码收藏代码
  1. voidMessagePumpDefault::ScheduleWork(){
  2. //Sincethiscanbecalledonanythread,weneedtoensurethatourRun
  3. //loopwakesup.
  4. event_.Signal();
  5. }
  6. voidMessagePumpForUI::ScheduleWork(){
  7. PostMessage(message_hwnd_,kMsgHaveWork,reinterpret_cast<WPARAM>(this),0);
  8. }
  9. voidMessagePumpForIO::ScheduleWork(){
  10. BOOLret=PostQueuedCompletionStatus(port_,0,
  11. reinterpret_cast<ULONG_PTR>(this),
  12. reinterpret_cast<OVERLAPPED*>(this));
  13. }




MessagePump对Native消息(Windows消息或IO完成消息)有自己的处理方式(PumpDefault没有消息要处理),而Task则由MessageLoop来统一处理。


Cpp代码收藏代码
  1. voidMessagePumpDefault::Run(Delegate*delegate){
  2. for(;;){
  3. ScopedNSAutoreleasePoolautorelease_pool;
  4. booldid_work=delegate->DoWork();
  5. if(!keep_running_)
  6. break;
  7. if(did_work)
  8. continue;
  9. did_work=delegate->DoIdleWork();
  10. if(!keep_running_)
  11. break;
  12. if(did_work)
  13. continue;
  14. if(delayed_work_time_.is_null()){
  15. event_.Wait();
  16. }else{
  17. TimeDeltadelay=delayed_work_time_-Time::Now();
  18. if(delay>TimeDelta()){
  19. event_.TimedWait(delay);
  20. }else{
  21. //Itlookslikedelayed_work_time_indicatesatimeinthepast,sowe
  22. //needtocallDoDelayedWorknow.
  23. delayed_work_time_=Time();
  24. }
  25. }
  26. //Sinceevent_isauto-reset,wedon'tneedtodoanythingspecialhere
  27. //otherthanserviceeachdelegatemethod.
  28. }
  29. keep_running_=true;
  30. }
  31. voidMessagePumpForUI::DoRunLoop(){
  32. for(;;){
  33. boolmore_work_is_plausible=ProcessNextWindowsMessage();
  34. if(state_->should_quit)
  35. break;
  36. if(more_work_is_plausible)
  37. continue;
  38. more_work_is_plausible=state_->delegate->DoIdleWork();
  39. if(state_->should_quit)
  40. break;
  41. if(more_work_is_plausible)
  42. continue;
  43. WaitForWork();//Wait(sleep)untilwehaveworktodoagain.
  44. }
  45. }
  46. voidMessagePumpForIO::DoRunLoop(){
  47. for(;;){
  48. boolmore_work_is_plausible=state_->delegate->DoWork();
  49. if(state_->should_quit)
  50. break;
  51. more_work_is_plausible|=WaitForIOCompletion(0,NULL);
  52. if(state_->should_quit)
  53. break;
  54. if(more_work_is_plausible)
  55. continue;
  56. more_work_is_plausible=state_->delegate->DoIdleWork();
  57. if(state_->should_quit)
  58. break;
  59. if(more_work_is_plausible)
  60. continue;
  61. WaitForWork();//Wait(sleep)untilwehaveworktodoagain.
  62. }
  63. }


MessageLoop对象从队列里取出Task执行,即调用Task::Run,然后删除。


Cpp代码收藏代码
  1. voidMessageLoop::RunTask(Task*task){
  2. task->Run();
  3. deletetask;
  4. }





当一条线程创建了MessageLoop对象之后,它就具备了运行Task的能力,我们就可以任意指派Task在该线程执行。就像这样


Cpp代码收藏代码
  1. 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消息,不需要额外的注册过程。


Cpp代码收藏代码
  1. classMessagePumpForIO:publicMessagePumpWin{
  2. classIOHandler{
  3. public:
  4. virtual~IOHandler(){}
  5. //ThiswillbecalledoncethependingIOoperationassociatedwith
  6. //|context|completes.|error|istheWin32errorcodeoftheIOoperation
  7. //(ERROR_SUCCESSiftherewasnoerror).|bytes_transfered|willbezero
  8. //onerror.
  9. virtualvoidOnIOCompleted(IOContext*context,DWORDbytes_transfered,
  10. DWORDerror)=0;
  11. };
  12. voidRegisterIOHandler(HANDLEfile_handle,IOHandler*handler);
  13. }



所以,如果相关模块都可以异步调用,那么只要有两条线程(分别处理UI和IO消息)即可满足程序的需要,而主线程一般就是UI线程。 使用基于MessageLoop的消息分发机制,可以大大减少线程的数量,避免程序并发带来的各种问题。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics