Чем объясняется такое странное поведение PeekMessage (попытка работать с полной очередью сообщений, фильтрация определенных сообщений)?

Наше приложение работает как COM-сервер, где вся автоматизация происходит в пределах одной квартиры STA (в главном потоке приложения), и некоторые VBS-скрипты, которые делают длительные (>10 минут) вызовы, терпят неудачу с ошибкой "System call failed (80010100)". Некоторые исследования (один, два, три) показывают, что это, вероятно, вызвано заполнением очереди сообщений, так что когда COM пытается вызвать следующий метод, он не может этого сделать.

Если это важно, приложение разработано в Embarcadero RAD Studio 2010 (в основном C++, немного Delphi для некоторых COM-классов)

Я решил исследовать очередь сообщений потока в конце длинного вызова метода COM (т.е., непосредственно перед возвратом), чтобы посмотреть, что она содержит, используя GetQueueStatus и PeekMessage. Хотя кажется, что очередь заполнена, я наблюдаю некоторое странное поведение, и мне трудно понять, почему PeekMessage ведет себя так, как ведет, и почему именно очередь заполнена - то есть, чем она заполнена.

Немного длинное объяснение впереди:

Проверка того, что очередь сообщений потока заполнена

Код, подобный этому:

int iMessages = 0;
DWORD dwThreadId = GetCurrentThreadId();
while (::PostThreadMessage(dwThreadId, WM_USER, 0, 0)) {
  iMessages++;
}
if (GetLastError() == ERROR_NOT_ENOUGH_QUOTA) {
  String strError = L"Not enough quota, posted " + IntToStr(iMessages) + L" messages";
  // Do something with strError
}

при запуске в конце короткого метода, вызванного COM, может отправить тысячи (скажем, 9996) сообщений; в конце длинного вызова метода, который вызывает сбой скрипта, он может отправить 0. Мой вывод заключается в том, что заполненность очереди сообщений действительно является причиной проблемы. Лимит очереди сообщений в моей системе по умолчанию 10000 (см. раздел Remarks.)

Вызов Application->ProcessMessages() (вызывает цикл сообщений приложения, пока он не опустеет, для тех из вас, кто не является пользователем Delphi / C++Builder - это вполне обычный метод "получить/перевести/отправить, пока больше нет сообщений") решает проблему, и COM-скрипт может успешно вызвать следующий метод. Хотя, возможно, в данной конкретной ситуации все в порядке, вызова ProcessMessages() в фактически случайных местах следует избегать - это может привести к реентерабельности. Я бы хотел выяснить причину переполнения очереди, если это возможно.

Изучение содержимого очереди сообщений

Использование GetQueueStatus для определения типа сообщений в очереди показывает, что есть таймер (QS_TIMER), опубликованные сообщения (QS_POSTMESSAGE), 'все опубликованные сообщения' (т.е. другие опубликованные сообщения, ). т.е. другие опубликованные, QS_ALLPOSTMESSAGE), и сообщения рисования (QS_PAINT).

Вот где все становится странным. Я пытаюсь удалить выбранные сообщения или типы сообщений, используя PeekMessage с PM_REMOVE для частичного опустошения очереди и подсчета количества сообщений каждого типа.

Если я вызываю:

while (::PeekMessage(&oMsg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD | (QS_TIMER << 16)) != 0) {...

я получаю чуть больше десяти тысяч сообщений, обычно 10006 или около того. Не все из них - WM_TIMER: несколько тысяч - WM_APP+202, сообщение, которое мы используем внутри компании и которое не , похоже, отправляется (нами) в таких огромных количествах. Я проверил это: оно отправляется всего несколько раз. Есть также несколько тысяч других сообщений WM_APP+something, которые мы используем; это сообщение, вероятно, действительно отправляется слишком часто.

Если я назову это вместо :

while (::PeekMessage(&oMsg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE | PM_NOYIELD) != 0) {...

я получу около десяти сообщений, все из которых действительно WM_TIMER. Почему? Документация PeekMessage указывает, что передача QS_TIMER

Наконец, если вместо этого я вызываю третий вариант,

while (::PeekMessage(&oMsg, NULL, WM_APP+202, WM_APP+202, PM_REMOVE | PM_NOYIELD) != 0) {...

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

Я воспроизводил все это несколько раз - ничего из этого ни разу не...отклонение поведения.

Итак:

  • Почему первый вызов PeekMessage удаляет не только таймеры (по сравнению со вторым вызовом)? Только из любопытства, на самом деле.
  • Почему первый вызов PeekMessage удаляет несколько тысяч сообщений WM_APP+202 (которые мы определяем и используем и не посылаем так много), но если я вместо этого вызову третий вариант, который фильтрует непосредственно это конкретное сообщение, я получу 17?
  • Если я получаю такие разные результаты для одного и того же сообщения, как мне выяснить, что заполнило очередь и как лучше избежать этого?
  • Или, чтобы избежать всего вышеперечисленного: могу ли я спокойно игнорировать все это, и тогда как мне быть с полной очередью сообщений, когда COM собирается попытаться вызвать метод?

Я в недоумении, и вполне возможно, что делаю элементарную ошибку - дошел до стадии разглядывания чего-то загадочного, когда так поступаешь. Любая помощь в решении проблемы с COM или объяснения поведения сообщений, включая "Вы совершили элементарную ошибку X, черт возьми, это было глупо с вашей стороны", будут очень признательны :)

6
задан Community 23 May 2017 в 11:53
поделиться