PostMessage иногда теряет сообщение

Я написал многопоточное приложение для Windows, в котором поток:
A - это форма окна, которая обрабатывает взаимодействие с пользователем и обрабатывает данные из B.
B - иногда генерирует данные и передает им два A.

Потокобезопасная очередь используется для передачи данных из потока B в A. Функции постановки в очередь и удаления из очереди охраняются с помощью объектов критического раздела Windows.

Если при вызове функции постановки в очередь очередь пуста, функция будет использовать PostMessage, чтобы сообщить A, что в очереди есть данные. Функция проверяет, успешно ли выполняется вызов PostMessage, и повторно вызывает PostMessage, если он не был успешным (PostMessage еще не завершился ошибкой).

Некоторое время это работало хорошо, пока один конкретный компьютер не начал терять случайные сообщения. Под проигрышем я подразумеваю, что PostMessage успешно возвращается в B, но A никогда не получает сообщение. Это приводит к зависанию программного обеспечения.

Я уже придумал пару приемлемых обходных путей. Мне интересно знать, почему окна теряют эти сообщения и почему это происходит только на одном компьютере.

Вот соответствующие части кода.

// Only called by B
procedure TSharedQueue.Enqueue(AItem: TSQItem);
var
 B: boolean;
begin
  EnterCriticalSection(FQueueLock);
  if FCount > 0 then
    begin
      FLast.FNext := AItem;
      FLast := AItem;
    end
  else
    begin
      FFirst := AItem;
      FLast := AItem;
    end;

  if (FCount = 0) or (FCount mod 10 = 0) then // just in case a message is lost
    repeat
      B := PostMessage(FConsumer, SQ_HAS_DATA, 0, 0);
      if not B then 
  Sleep(1000); // this line of code has never been reached
    until B;

  Inc(FCount);
  LeaveCriticalSection(FQueueLock);
end;

// Only called by A 
function TSharedQueue.Dequeue: TSQItem;
begin
  EnterCriticalSection(FQueueLock);
  if FCount > 0 then
    begin
      Result := FFirst;
      FFirst := FFirst.FNext;
      Result.FNext := nil;
      Dec(FCount);
    end
  else
    Result := nil;
  LeaveCriticalSection(FQueueLock);
end;

// procedure called when SQ_HAS_DATA is received
procedure TfrmMonitor.SQHasData(var AMessage: TMessage);
var
  Item: TSQItem;
begin
  while FMessageQueue.Count > 0 do
    begin
      Item := FMessageQueue.Dequeue;
      // use the Item somehow
    end;
end;

Было бы полезно, если бы вы могли предоставить фрагменты кода критических областей, в которых вы блокируете / разблокируете / нажимаете / выталкиваете / проверяете очередь.

Ates Goral 15.01.2009 01:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
3 233
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Может ли быть второй экземпляр, который бессознательно запускает и поглощает сообщения, отмечая их как обработанные?

If the queue is empty when the enqueue function is called, the function will use PostMessage to tell A that there is data in the queue.

Вы блокируете очередь сообщений перед проверкой размера очереди и выдачей PostMessage? У вас может быть состояние гонки, когда вы проверяете очередь и находите ее непустой, когда на самом деле A обрабатывает самое последнее сообщение и собирается перейти в режим ожидания.

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

This worked well for quite some time until one specific computer started to lose the occasional message.

Случайно, отличается ли количество процессоров или ядер на этом конкретном компьютере от других, где вы не видите проблем? Иногда, когда вы переключаетесь с однопроцессорной машины на машину с более чем одним физическим процессором / ядром, могут возникнуть новые условия гонки или взаимоблокировки.

Ваш ответ не имеет смысла для меня: очередь сообщений, в которую PostMessage API отправляет сообщения, контролируется O / S (а не приложением) и не может быть «заблокирована» приложением.

ChrisW 15.01.2009 01:16

@ChrisW: Это утверждение OP: «Потокообезопасная очередь используется для передачи данных из потока B в A. Функции постановки в очередь и удаления из очереди охраняются с помощью объектов критического раздела Windows».

Ates Goral 15.01.2009 01:17

Насколько я понимаю, для постановки сообщений в очередь используется отдельная потокобезопасная очередь, а сообщение Windows, отправленное с помощью PostMessage, используется только в качестве сигнала, чтобы поток проснулся и обработал сообщения в очереди.

Ates Goral 15.01.2009 01:18
Ответ принят как подходящий

FCount также защищен FQueueLock? Если нет, то ваша проблема заключается в том, что FCount увеличивается после того, как отправленное сообщение уже обработано.

Вот что могло произойти:

  1. B входит в критическую секцию
  2. B звонит PostMessage
  3. A получает сообщение, но ничего не делает, так как FCount - это 0
  4. B с шагом FCount
  5. B покидает критическую секцию
  6. Сидит как утка

Быстрое решение - увеличить FCount перед вызовом PostMessage.

Имейте в виду, что все может произойти быстрее, чем можно было бы ожидать (то есть сообщение, отправленное с PostMessage, перехватывается и обрабатывается другим потоком, прежде чем у вас будет возможность увеличить FCount несколькими строками позже), особенно когда вы находитесь в истинном мульти- поточная среда (несколько процессоров). Вот почему я раньше спросил, есть ли у «проблемной машины» несколько процессоров / ядер.

Простым способом устранения подобных проблем является создание шаблона кода с дополнительным ведением журнала для ведения журнала каждый раз, когда вы вводите метод, входите / выходите из критического раздела и т. д. Затем вы можете проанализировать журнал, чтобы увидеть истинный порядок событий.

Отдельно отметим, что небольшая приятная оптимизация, которая может быть выполнена в сценарии производителя / потребителя, подобного этому, заключается в использовании двух очередей вместо одной. Когда потребитель просыпается для обработки полной очереди, вы заменяете полную очередь пустой и просто блокируете / обрабатываете полную очередь, в то время как новая пустая очередь может быть заполнена без того, чтобы два потока пытались заблокировать очереди друг друга. Однако вам все равно потребуется некоторая блокировка при обмене местами двух очередей.

@WarrenP С удовольствием :)

Ates Goral 02.11.2013 05:19

Другие вопросы по теме