понедельник, 1 марта 2010 г.

MsgWaitForMultipleObjects и состояние очереди

Это перевод MsgWaitForMultipleObjects and the queue state. Автор: Реймонд Чен.

Одной опасностью функции MsgWaitForMultipleObjects является её вызов, когда в очереди есть сообщения для обработки - потому что MsgWaitForMultipleObjects возвращает управление, только когда у нас есть новые сообщения.

Другими словами, рассмотрим такой сценарий:
  • PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) возвращает True, говоря, что у нас есть сообщение.
  • Вместо обработки сообщения, вы игнорируете его и вызываете MsgWaitForMultipleObjects.
Это ожидание не завершится тут же, хотя у нас есть сообщение в очереди. Это потому что вызов PeekMessage сообщил вам, что сообщение готово, и вы сами проигнорировали его. Функция MsgWaitForMultipleObjects сообщает вам только о новых сообщениях; все сообщения, о которых вы уже в курсе - не считаются.

Частый вариант этих действий выглядит так:
  • MsgWaitForMultipleObjects возвращает управление, сообщая, что у нас есть сообщение.
  • Вы вызываете PeekMessage(Msg, 0, 0, 0, PM_REMOVE) и обрабатываете это сообщение.
  • Вы вызываете MsgWaitForMultipleObject, чтобы ждать следующее сообщение.
Если произойдёт так, что у вас в очереди было два сообщения, то второй вызов MsgWaitForMultipleObjects не вернёт управление немедленно, потому что у нас нет новых сообщений; однако, у вас есть старое сообщение, которое вы сами проигнорировали.

Когда MsgWaitForMultipleObjects говорит вам, что у вас есть сообщение в очереди, вы должны обрабатывать все сообщения, пока PeekMessage не вернёт False, говоря, что сообщений больше нет.

Заметьте, однако, что такая последовательность не является проблемой:
  • PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) возвращает False, указывая, что сообщений больше нет.
  • Сообщение post-тся в вашу очередь.
  • Вы вызываете MsgWaitForMultipleObjects и включаете флаг QS_ALLPOSTMESSAGE.
Это ожидание вернёт управление немедленно, потому что входящее post-сообщение устанавливает признак "в очереди есть новое сообщение, о котором никто не знает", что согласуется с флагом QS_ALLPOSTMESSAGE и приводит к тому, что MsgWaitForMultipleObjects возвращает управление немедленно.

Функция MsgWaitForMultipleObjectsEx позволяет вам указать флаг MWMO_INPUTAVAILABLE, чтобы указать, что она должна проверять ранее игнорированный ввод.

Вооружённые этим знанием, объясните, почему наблюдаемое поведение нижеследующего кода таково: "иногда моя программа застревает и сообщает на одну запись меньше, чем должна. Мне приходится подёргать мышью, чтобы обновились значения. Через некоторое время она отстаёт на два значения, а потом - на три..."

// Предположим, что у нас есть рабочий поток, который обрабатывает записи и
// отправляет сообщение WM_NEWRECORD для каждой новой записи

function WaitForNRecords(h: THandle; cRecordsExpected: Integer): Boolean;
var
Msg: TMessage;
cRecords: Integer;
begin
cRecords := 0;
while True do
begin
case MsgWaitForMultipleObjects(1, h, False, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0:
DoSomethingWith(h); // сработало событие
WAIT_OBJECT_1:
begin
// У нас есть сообщение - обрабатываем его
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
if SendMessage(hwndNotify, WM_GETRECORDCOUNT, 0, 0) >= cRecordsExpected then
Exit(True); // у нас достаточно записей
end;
else
Exit(False); // неожиданная ошибка
end;
end;
end;

2 комментария:

  1. Хорошая задачка :)
    надо заменить
    // У нас есть сообщение - обрабатываем его
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
    на
    // У нас есть сообщение - обрабатываем его
    while not PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do

    ОтветитьУдалить
    Ответы
    1. Только не "while not PeekMessage", а "while PeekMessage".

      Удалить

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

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

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

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

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

Примечание. Отправлять комментарии могут только участники этого блога.