Я написал многопоточное приложение для 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;





Может ли быть второй экземпляр, который бессознательно запускает и поглощает сообщения, отмечая их как обработанные?
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: Это утверждение OP: «Потокообезопасная очередь используется для передачи данных из потока B в A. Функции постановки в очередь и удаления из очереди охраняются с помощью объектов критического раздела Windows».
Насколько я понимаю, для постановки сообщений в очередь используется отдельная потокобезопасная очередь, а сообщение Windows, отправленное с помощью PostMessage, используется только в качестве сигнала, чтобы поток проснулся и обработал сообщения в очереди.
FCount также защищен FQueueLock? Если нет, то ваша проблема заключается в том, что FCount увеличивается после того, как отправленное сообщение уже обработано.
Вот что могло произойти:
PostMessageFCount - это 0FCountБыстрое решение - увеличить FCount перед вызовом PostMessage.
Имейте в виду, что все может произойти быстрее, чем можно было бы ожидать (то есть сообщение, отправленное с PostMessage, перехватывается и обрабатывается другим потоком, прежде чем у вас будет возможность увеличить FCount несколькими строками позже), особенно когда вы находитесь в истинном мульти- поточная среда (несколько процессоров). Вот почему я раньше спросил, есть ли у «проблемной машины» несколько процессоров / ядер.
Простым способом устранения подобных проблем является создание шаблона кода с дополнительным ведением журнала для ведения журнала каждый раз, когда вы вводите метод, входите / выходите из критического раздела и т. д. Затем вы можете проанализировать журнал, чтобы увидеть истинный порядок событий.
Отдельно отметим, что небольшая приятная оптимизация, которая может быть выполнена в сценарии производителя / потребителя, подобного этому, заключается в использовании двух очередей вместо одной. Когда потребитель просыпается для обработки полной очереди, вы заменяете полную очередь пустой и просто блокируете / обрабатываете полную очередь, в то время как новая пустая очередь может быть заполнена без того, чтобы два потока пытались заблокировать очереди друг друга. Однако вам все равно потребуется некоторая блокировка при обмене местами двух очередей.
@WarrenP С удовольствием :)
Было бы полезно, если бы вы могли предоставить фрагменты кода критических областей, в которых вы блокируете / разблокируете / нажимаете / выталкиваете / проверяете очередь.