среда, 21 апреля 2010 г.

Обработка сообщений в течение заданного времени

Это перевод Pumping messages while waiting for a period of time. Автор: Реймонд Чен.

Мы можем использовать функцию MsgWaitForMultipleObjects (или её расширенный вариант MsgWaitForMultipleObjectsEx) для создания функции "sleep с обработкой сообщений" без использования опроса.

function SleepMsg(const dwTimeout: Cardinal): Boolean;
const
  MSGF_SLEEPMSG = $5300;
  MWMO_WAITANY = $0000;
  MWMO_INPUTAVAILABLE = $0004;
var
  dwStart, dwElapsed, dwStatus: Cardinal;
  Msg: TMsg;
begin
  dwStart := GetTickCount;
  dwElapsed := 0;
  while dwElapsed < dwTimeout do
  begin
    dwStatus := MsgWaitForMultipleObjectsEx(0, Pointer(nil)^, dwTimeout - dwElapsed, QS_ALLINPUT, MWMO_WAITANY or MWMO_INPUTAVAILABLE);
    if dwStatus = WAIT_OBJECT_0 then
    begin
      while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
      begin
        if Msg.Message = WM_QUIT then
        begin
          PostQuitMessage(Msg.wParam);
          Exit(False); // прервались из-за WM_QUIT
        end;
        if not CallMsgFilter(Msg, MSGF_SLEEPMSG) then
        begin
          TranslateMessage(Msg);
          DispatchMessage(Msg);
        end;
      end;
    end;
    dwElapsed := GetTickCount - dwStart;
  end;
  Result := True; // таймаут
end;
Эта функция обрабатывает (pumps) сообщения в течение dwTimeout миллисекунд. Ядро идеи заключается в использовании функции MsgWaitForMultipleObjects/Ex в качестве суррогата для WaitMessageTimeout и обработки сообщений, пока не будет достигнут суммарный таймаут. Однако, тут есть много мелких деталей, на которые нужно обращать внимание. Я привожу ссылки на предыдущие сообщения, если вы захотите освежить их в памяти: Вы можете решить, что CallMsgFilter тут излишен, но вы передумаете, когда осознаете, что пользователи могут нажать клавиатурный акселератор, пока вы спите, и вы, вероятно, хотели бы, чтобы он прошёл через чей-то TranslateAccelerator. Фильтр сообщений позволяет вам внедриться в модальный цикл и сделать трансляцию акселератора.

Расширение этой функции до "ждать на множестве описателей указанное количество времени, обрабатывая в это время сообщения" я оставляю вам в качестве упражнения (вы можете это сделать, не изменяя много строк кода).

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

  1. Анонимный3 июня 2013 г., 2:45

    1. В строке while (GetTickCount - dwStart) < dwTimeout) do - не хватает открывающей скобки "(" после while.
    2. Данная функция хорошо работает при входном значении от 20-25мсек! Для меньшего входного значения - обработка самой функции даёт слишком большую погрешность, вот код для проверки:

    procedure TForm1.Button1Click(Sender: TObject);
    var
    aa, bb, dd, ii: Integer;
    begin
    Form1.Memo1.Lines.Append('Запуск процедуры - ' + DateTimeToStr(now));
    bb :=0;
    for aa := 0 to 1000 do
    begin
    //Sleep(1); // быстрый вариант
    //application.processmessages; // быстрый вариант
    SleepMsg(1); // альтернативный, медленный вариант

    bb := aa+bb;
    for dd := 0 to 10 do
    bb := aa+bb;

    Form1.Memo1.Lines.Append('----- '+ IntToStr (bb));
    end;
    Form1.Memo1.Lines.Append('Финиш процедуры - ' + DateTimeToStr(now));
    end;

    ОтветитьУдалить
  2. Это, скорее, лишняя закрывающая скобка :) Исправил.

    Насчёт времени: и Sleep, и GetTickCount, и MsgWaitForMultipleObjectsEx используют системный таймер для измерения времени. Один "тик" системного таймера может быть от 10 до 55 мс.

    Я не знаю, в чём может быть отличие по измерению таймаута в Sleep и MsgWaitForMultipleObjectsEx, но в любом случае я не ожидал бы точного измерения интервалов менее 60 мс от этих функций.

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

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

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

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

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

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