Chrome中的消息循環


主要是自己做個學習筆記吧,我經驗也不是很豐富,以前學習多線程的時候就感覺寫多線程程序很麻煩。主要是線程之間要通信,要切線程,要同步,各種麻煩。我本身的工作經歷決定了也沒有太多的工作經驗,所以chrome的messageloop可以說是我用到的第一個成熟的線程消息封裝庫,用的很簡單,舒服。主要涉及MessageLoop和MessagePump這兩個類系。

以前不太清楚chrome當時在設計這兩個類時是如何分工的,今天又看了一下代碼,有了點感覺。MessagePump主要用來做消息循環, 與操作系統等平臺相關的部分都在MessagePump類裏, 針對不同的平臺有不同的實現,對messageloop封裝了平臺的不一致性。而MessageLoop主要是處理chrome自己的task機制的,這一部分。我們以windows平臺來進行代碼分析, MessagePump中的DoRunLoop是每個線程進行消息循環處理的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void MessagePumpForUI::DoRunLoop() {
 
  for (;;) {
    // If we do any work, we may create more messages etc., and more work may
    // possibly be waiting in another task group.  When we (for example)
    // ProcessNextWindowsMessage(), there is a good chance there are still more
    // messages waiting.  On the other hand, when any of these methods return
    // having done no work, then it is pretty unlikely that calling them again
    // quickly will find any work to do.  Finally, if they all say they had no
    // work, then it is a good time to consider sleeping (waiting) for more
    // work.
 
    bool more_work_is_plausible = ProcessNextWindowsMessage();
    if (state_->should_quit)
      break;
 
    more_work_is_plausible |= state_->delegate->DoWork();
    if (state_->should_quit)
      break;
 
    more_work_is_plausible |=
        state_->delegate->DoDelayedWork(&delayed_work_time_);
    // If we did not process any delayed work, then we can assume that our
    // existing WM_TIMER if any will fire when delayed work should run.  We
    // don't want to disturb that timer if it is already in flight.  However,
    // if we did do all remaining delayed work, then lets kill the WM_TIMER.
    if (more_work_is_plausible && delayed_work_time_.is_null())
      KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
    if (state_->should_quit)
      break;
 
    if (more_work_is_plausible)
      continue;
 
    more_work_is_plausible = state_->delegate->DoIdleWork();
    if (state_->should_quit)
      break;
 
    if (more_work_is_plausible)
      continue;
 
    WaitForWork();  // Wait (sleep) until we have work to do again.
  }
}

這塊兒代碼通過一個for的死循環來維持線程的運行, 同時進行系統消息的處理和task的處理,從代碼看可以分爲循環可以分爲如下幾個部分:僞碼描述: 

複製代碼
for(;;)
{
  處理windows系統消息

  執行task隊列中的一個task

  執行delayedTask隊列中的一個task。

  if(還有其他任務(more_work_is_pausiable)), 
    continue;
 else
   掛起線程,等待消息進行喚醒
}
複製代碼

 

下面分別從幾部分進行分析:

處理windows消息 

循環先從Windows的消息隊列中提取下一條消息進行處理。

1
bool more_work_is_plausible = ProcessNextWindowsMessage();
複製代碼
bool MessagePumpForUI::ProcessNextWindowsMessage() {
  // If there are sent messages in the queue then PeekMessage internally
  // dispatches the message and returns false. We return true in this
  // case to ensure that the message loop peeks again instead of calling
  // MsgWaitForMultipleObjectsEx again.
  bool sent_messages_in_queue = false;
  DWORD queue_status = GetQueueStatus(QS_SENDMESSAGE);
  if (HIWORD(queue_status) & QS_SENDMESSAGE)
    sent_messages_in_queue = true;

  MSG msg;
  if (message_filter_->DoPeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    return ProcessMessageHelper(msg);

  return sent_messages_in_queue;
}
複製代碼

 在processNextWindowsMessage函數中主要處理 window的窗口消息,它的返回值 表示輸入隊列中是否還有其他消息待處理,這樣可以避免多調用一次MsgWaitForMultipleObjectsEx。

具體的ProcessMessageHelper代碼如下,單獨的WM_QUIT來進行單獨推出處理。

複製代碼
bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) {
  TRACE_EVENT1("base", "MessagePumpForUI::ProcessMessageHelper",
               "message", msg.message);
  if (WM_QUIT == msg.message) {
    state_->should_quit = true;
    PostQuitMessage(static_cast<int>(msg.wParam));
    return false;
  }

  // While running our main message pump, we discard kMsgHaveWork messages.
  if (msg.message == kMsgHaveWork && msg.hwnd == message_hwnd_)
    return ProcessPumpReplacementMessage();

  if (CallMsgFilter(const_cast<MSG*>(&msg), kMessageFilterCode))
    return true;

  WillProcessMessage(msg);

  if (!message_filter_->ProcessMessage(msg)) {
    if (state_->dispatcher) {
      if (!state_->dispatcher->Dispatch(msg))
        state_->should_quit = true;
    } else {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  DidProcessMessage(msg);
  return true;
}
複製代碼

 

執行task 

處理了一個windows消息後, 然後通過MessagePump::Delegate的接口,調用MessageLoop的DoWork操作,來處理task隊列。

1
more_work_is_plausible |= state_->delegate->DoWork(); 
複製代碼
bool MessageLoop::DoWork() {
  if (!nestable_tasks_allowed_) {
    // Task can't be executed right now.
    return false;
  }

  for (;;) {
    ReloadWorkQueue();
    if (work_queue_.empty())
      break;

    // Execute oldest task.
    do {
      PendingTask pending_task = work_queue_.front();
      work_queue_.pop();
      if (!pending_task.delayed_run_time.is_null()) {
        AddToDelayedWorkQueue(pending_task);
        // If we changed the topmost task, then it is time to reschedule.
        if (delayed_work_queue_.top().task.Equals(pending_task.task))
          pump_->ScheduleDelayedWork(pending_task.delayed_run_time);
      } else {
        if (DeferOrRunPendingTask(pending_task))
          return true;
      }
    } while (!work_queue_.empty());
  }

  // Nothing happened.
  return false;
}
複製代碼

這個函數中通過循環來找到一個處理當前taskQueue的一個task進行執行, 將delayed的task 存入DelayedWorkQueue.

 執行DelayedTask

 

複製代碼
bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) {
  if (!nestable_tasks_allowed_ || delayed_work_queue_.empty()) {
    recent_time_ = *next_delayed_work_time = TimeTicks();
    return false;
  }

  // When we "fall behind," there will be a lot of tasks in the delayed work
  // queue that are ready to run.  To increase efficiency when we fall behind,
  // we will only call Time::Now() intermittently, and then process all tasks
  // that are ready to run before calling it again.  As a result, the more we
  // fall behind (and have a lot of ready-to-run delayed tasks), the more
  // efficient we'll be at handling the tasks.

  TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time;
  if (next_run_time > recent_time_) {
    recent_time_ = TimeTicks::Now();  // Get a better view of Now();
    if (next_run_time > recent_time_) {
      *next_delayed_work_time = next_run_time;
      return false;
    }
  }

  PendingTask pending_task = delayed_work_queue_.top();
  delayed_work_queue_.pop();

  if (!delayed_work_queue_.empty())
    *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time;

  return DeferOrRunPendingTask(pending_task);
}
複製代碼

 

 比對時間,如果到了delayedtask的執行時機,執行delayed task。

 掛起等待用戶輸入消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void MessagePumpForUI::WaitForWork() {
  // Wait until a message is available, up to the time needed by the timer
  // manager to fire the next set of timers.
  int delay = GetCurrentDelay();
  if (delay < 0)  // Negative value means no timers waiting.
    delay = INFINITE;
 
  DWORD result;
  result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT,
                                       MWMO_INPUTAVAILABLE);
 
  if (WAIT_OBJECT_0 == result) {
    // A WM_* message is available.
    // If a parent child relationship exists between windows across threads
    // then their thread inputs are implicitly attached.
    // This causes the MsgWaitForMultipleObjectsEx API to return indicating
    // that messages are ready for processing (Specifically, mouse messages
    // intended for the child window may appear if the child window has
    // capture).
    // The subsequent PeekMessages call may fail to return any messages thus
    // causing us to enter a tight loop at times.
    // The WaitMessage call below is a workaround to give the child window
    // some time to process its input messages.
    MSG msg = {0};
    DWORD queue_status = GetQueueStatus(QS_MOUSE);
    if (HIWORD(queue_status) & QS_MOUSE &&
        !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {
      WaitMessage();
    }
    return;
  }
 
  DCHECK_NE(WAIT_FAILED, result) << GetLastError();
}

  通過MsgWaitForMultiPleObjectsEx來進行掛起等待。

語言表達能力不行,不知到說清楚沒有 。 不清楚在補吧 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章