Chromium MessageLoop类分析

原文:

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

Cpp代码   收藏代码
  1. MSG msg;  
  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. unsigned int WINAPI ThreadFunc(LPVOID param)  
  2. {  
  3. ...   
  4.   
  5.   
  6. //执行到这里,线程就准备退出了  
  7. }  
 

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


Cpp代码   收藏代码
  1. unsigned int WINAPI ThreadFunc(LPVOID param)  
  2. {  
  3. ...   
  4.   
  5.   
  6. while(WaitForMessage())   
  7. {  
  8. DispatchMessage();   
  9. }  
  10.   
  11. }  
 


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




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




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


Cpp代码   收藏代码
  1. class MessageLoop : public base::MessagePump::Delegate {  
  2. {  
  3. public:  
  4.   
  5.   
  6. // Run the message loop.  
  7. void Run();  
  8.   
  9.   
  10. // Signals the Run method to return after it is done processing all pending  
  11. // messages. This method may only be called on the same thread that called  
  12. // Run, and Run must still be on the call stack.  
  13. //  
  14. // Use QuitTask if you need to Quit another thread's MessageLoop, but note  
  15. // that doing so is fairly dangerous if the target thread makes nested calls  
  16. // to MessageLoop::Run. The problem being that you won't know which nested  
  17. // run loop you are quiting, so be careful!  
  18. //  
  19. void Quit();  
  20.   
  21.   
  22. // Returns the MessageLoop object for the current thread, or null if none.  
  23. static MessageLoop* current();  
  24.   
  25.   
  26.   
  27.   
  28. // The "PostTask" family of methods call the task's Run method asynchronously  
  29. // from within a message loop at some point in the future.  
  30. //  
  31. // With the PostTask variant, tasks are invoked in FIFO order, inter-mixed  
  32. // with normal UI or IO event processing.   
  33. //  
  34. // The MessageLoop takes ownership of the Task, and deletes it after it has  
  35. // been Run().  
  36. //  
  37. // NOTE: These methods may be called on any thread. The Task will be invoked  
  38. // on the thread that executes MessageLoop::Run().  
  39.   
  40.   
  41. void PostTask( const tracked_objects::Location& from_here, Task* task);  
  42. }  
 


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


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




Cpp代码   收藏代码
  1. MessageLoop::MessageLoop(Type type)  
  2. {  
  3. lazy_tls_ptr.Pointer()->Set(this);  
  4.   
  5.   
  6. if (type_ == TYPE_DEFAULT) {  
  7. pump_ = new base::MessagePumpDefault();  
  8. else if (type_ == TYPE_IO) {  
  9. pump_ = new base::MessagePumpForIO();  
  10. else {  
  11. DCHECK(type_ == TYPE_UI);  
  12. pump_ = new base::MessagePumpForUI();  
  13. }  
  14. }  
  15.   
  16.   
  17. void MessageLoop::RunInternal()   
  18. {  
  19. pump_->Run(this);  
  20. }  
 


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


Cpp代码   收藏代码
  1. void MessagePumpDefault::Run(Delegate* delegate) {  
  2.   
  3. if (delayed_work_time_.is_null()) {  
  4. event_.Wait();  
  5. else {  
  6. TimeDelta delay = delayed_work_time_ - Time::Now();  
  7. if (delay > TimeDelta()) {  
  8. event_.TimedWait(delay);  
  9. else {  
  10. delayed_work_time_ = Time();  
  11. }  
  12. }  
  13. }  
  14.   
  15.   
  16. void MessagePumpForUI::WaitForWork() {  
  17.   
  18.   
  19. DWORD result;  
  20. result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT,  
  21. MWMO_INPUTAVAILABLE);  
  22.   
  23.   
  24. if (WAIT_OBJECT_0 == result) {  
  25. MSG msg = {0};  
  26. DWORD queue_status = GetQueueStatus(QS_MOUSE);  
  27. if (HIWORD(queue_status) & QS_MOUSE &&  
  28. !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {  
  29. WaitMessage();  
  30. }  
  31. return;  
  32. }  
  33.   
  34.   
  35.   
  36.   
  37. bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) {  
  38. memset(item, 0, sizeof(*item));  
  39. ULONG_PTR key = NULL;  
  40. OVERLAPPED* overlapped = NULL;  
  41. if (!GetQueuedCompletionStatus(port_.Get(), &item->bytes_transfered, &key,  
  42. &overlapped, timeout)) {  
  43. if (!overlapped)  
  44. return false// Nothing in the queue.  
  45. item->error = GetLastError();  
  46. item->bytes_transfered = 0;  
  47. }  
  48.   
  49.   
  50. item->handler = reinterpret_cast<IOHandler*>(key);  
  51. item->context = reinterpret_cast<IOContext*>(overlapped);  
  52. return true;  
  53. }  
 




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


Cpp代码   收藏代码
  1. void MessagePumpDefault::ScheduleWork() {  
  2. // Since this can be called on any thread, we need to ensure that our Run  
  3. // loop wakes up.  
  4. event_.Signal();  
  5. }  
  6.   
  7.   
  8. void MessagePumpForUI::ScheduleWork() {  
  9. PostMessage(message_hwnd_, kMsgHaveWork, reinterpret_cast<WPARAM>(this), 0);  
  10. }  
  11.   
  12.   
  13. void MessagePumpForIO::ScheduleWork() {  
  14. BOOL ret = PostQueuedCompletionStatus(port_, 0,  
  15. reinterpret_cast<ULONG_PTR>(this),  
  16. reinterpret_cast<OVERLAPPED*>(this));  
  17. }  
 



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


Cpp代码   收藏代码
  1. void MessagePumpDefault::Run(Delegate* delegate) {  
  2.   
  3.   
  4. for (;;) {  
  5. ScopedNSAutoreleasePool autorelease_pool;  
  6.   
  7.   
  8. bool did_work = delegate->DoWork();  
  9. if (!keep_running_)  
  10. break;  
  11.   
  12.   
  13. if (did_work)  
  14. continue;  
  15.   
  16.   
  17. did_work = delegate->DoIdleWork();  
  18. if (!keep_running_)  
  19. break;  
  20.   
  21.   
  22. if (did_work)  
  23. continue;  
  24.   
  25.   
  26. if (delayed_work_time_.is_null()) {  
  27. event_.Wait();  
  28. else {  
  29. TimeDelta delay = delayed_work_time_ - Time::Now();  
  30. if (delay > TimeDelta()) {  
  31. event_.TimedWait(delay);  
  32. else {  
  33. // It looks like delayed_work_time_ indicates a time in the past, so we  
  34. // need to call DoDelayedWork now.  
  35. delayed_work_time_ = Time();  
  36. }  
  37. }  
  38. // Since event_ is auto-reset, we don't need to do anything special here  
  39. // other than service each delegate method.  
  40. }  
  41.   
  42.   
  43. keep_running_ = true;  
  44. }  
  45.   
  46.   
  47. void MessagePumpForUI::DoRunLoop() {  
  48. for (;;) {  
  49. bool more_work_is_plausible = ProcessNextWindowsMessage();  
  50. if (state_->should_quit)  
  51. break;  
  52.   
  53.   
  54. if (more_work_is_plausible)  
  55. continue;  
  56.   
  57.   
  58. more_work_is_plausible = state_->delegate->DoIdleWork();  
  59. if (state_->should_quit)  
  60. break;  
  61.   
  62.   
  63. if (more_work_is_plausible)  
  64. continue;  
  65.   
  66.   
  67. WaitForWork(); // Wait (sleep) until we have work to do again.  
  68. }  
  69. }  
  70.   
  71.   
  72.   
  73.   
  74. void MessagePumpForIO::DoRunLoop() {  
  75. for (;;) {  
  76. bool more_work_is_plausible = state_->delegate->DoWork();  
  77. if (state_->should_quit)  
  78. break;  
  79.   
  80.   
  81. more_work_is_plausible |= WaitForIOCompletion(0, NULL);  
  82. if (state_->should_quit)  
  83. break;  
  84.   
  85.   
  86. if (more_work_is_plausible)  
  87. continue;  
  88.   
  89.   
  90. more_work_is_plausible = state_->delegate->DoIdleWork();  
  91. if (state_->should_quit)  
  92. break;  
  93.   
  94.   
  95. if (more_work_is_plausible)  
  96. continue;  
  97.   
  98.   
  99. WaitForWork(); // Wait (sleep) until we have work to do again.  
  100. }  
  101. }  
 

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


Cpp代码   收藏代码
  1. void MessageLoop::RunTask(Task* task) {  
  2. task->Run();  
  3. delete task;  
  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. class MessagePumpForIO : public MessagePumpWin {  
  2.   
  3.   
  4. class IOHandler {  
  5. public:  
  6. virtual ~IOHandler() {}  
  7. // This will be called once the pending IO operation associated with  
  8. // |context| completes. |error| is the Win32 error code of the IO operation  
  9. // (ERROR_SUCCESS if there was no error). |bytes_transfered| will be zero  
  10. // on error.  
  11. virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered,  
  12. DWORD error) = 0;  
  13. };  
  14.   
  15.   
  16.   
  17.   
  18. void RegisterIOHandler(HANDLE file_handle, IOHandler* handler);  
  19.   
  20.   
  21. }  
 


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

你可能感兴趣的:(多线程,windows,工作,IO,null,delay)