воскресенье, 2 мая 2010 г.

Опасности сна в потоке с выборкой сообщений

Это перевод The dangers of sleeping on a UI thread. Автор: Реймонд Чен.

Если у вас есть поток, который владеет окном, то вам не следует использовать в нём функцию Sleep, потому что это приведёт к тому, что ваш поток перестанет отвечать на сообщения на время засыпания. Это также верно и для коротких задержек, вроде засыпания на несколько секунд, потом проверку чего-то и повторное засыпание. Как мы заметили ранее, опрос плохо влияет на производительность системы, не даёт ОС переходить в режим пониженного электро-потребления, а также плохо работает в сценариях с сервером терминалов. Если вы ждёте (idle) - то ждите. Если вы заняты - то сделайте свою работу, а потом ждите.

К сожалению, я часто вижу код вроде такого:
fQuit := False;
  while not fQuit do
  begin
    Sleep(2000);
    CheckIfSomethingHappened;
    while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
    begin
      if Msg.message = WM_QUIT then
      begin
        fQuit := True;
        Break;
      end;
      TranslateMessage(Msg);
      DispatchMessage(Msg);
    end;
  end;
Заметьте, что этот цикл проводит две секунды без обработки сообщений. Люди ещё не сошли с ума, чтобы вставлять двухсекундные задержки в поток, который отвечает за UI, но они часто так делают в фоновых рабочих потоках, которые создают невидимые окна для упрощения межпроцессного или межпоточного обмена данными. Поскольку у потока нет видимого интерфейса, то задержки в несколько секунд невидимы пользователю.

Вот только это не так.

Если системе нужно сделать массовую рассылку сообщений, то ей придётся ждать пробуждения потока, чтобы обработать сообщение. В это время, компонент, который запросил рассылку, вынужден ждать. Например, пользователь может дважды щёлкнуть по документу, который для открытия требует обмена DDE. DDE процесс начинает работу с массовой рассылки сообщения WM_DDE_INITIATE, которое теперь тормозит на вашем окне. Ваше спящее окно только что создало новый баг "Windows иногда замораживается на случайное время".

Заметьте, что многие люди не осознают, что вызов CoInitialize (возможно неявно) для инициализации потока для модели STA создаёт скрытое окно, чтобы выполнять маршалинг. Соответственно, поток, который запущен в одно-поточном окружении (single-threaded apartment) обязан обрабатывать сообщения. Если вы это не сделаете, то это приведёт к загадочным тормозам системы из-за вашего висящего окна.

Но что если вы хотите заснуть на какое-то время в своём потоке с выборкой сообщений? Мы разбирали это не так давно.

1 комментарий:

  1. Случайно натолкнулся на вашу статью, пусть не вовремя но сочту нужным высказать своё мнение, точнее дополнение.
    Приведённая выше петля обработки сообщений создана для обработки отложенных сообщений (отправленный процедурой PostMessage) и если все сообщения отправляются именно таким образом, то никаких проблем возникнуть не должно. Но если кто-то из другого потока отправит сообщение функцией SendMessage, то только тогда, как я понимаю, произойдёт задержка. Причина проста - все сообщения окна обрабатываются в потоке где живёт это окно и, соответственно, вызов SendMessage пойдёт по по пути постановки сообщения в очередь и ожидания его выполнения. что, как сказал автор, приведёт к задержке. Программист, написавший петлю обработки сообщений, использовал Sleep для экономии процессорного времени, что в принципе логично, за исключением проблем с задержкой выполнения. Обходится данная ситуация просто - Для получения очередного сообщения используйте функцию GetMessage. Функция ожидает появления в очереди сообщения. Механизм ожидания реализован на уровне системных объектов, по этому поток замрёт до появления сообщения. Функция возвращает Истину если сообщение получено либо Ложь если получено сообщение WM_Quit.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.