PostMessage 偶尔会丢失消息
-
22-07-2019 - |
题
我编写了一个多线程 Windows 应用程序,其中线程:
A – 是一个 Windows 窗体,用于处理用户交互并处理来自 B 的数据。
B – 偶尔生成数据并将其传递给两个 A。
线程安全队列用于将数据从线程 B 传递到 A。使用 Windows 临界区对象来保护入队和出队函数。
如果调用enqueue函数时队列为空,该函数将使用PostMessage告诉A队列中有数据。该函数检查以确保对 PostMessage 的调用已成功执行,如果不成功则重复调用 PostMessage(PostMessage 尚未失败)。
这种方法在相当长的一段时间内运作良好,直到一台特定的计算机开始偶尔丢失消息。我所说的丢失是指,PostMessage 在 B 中成功返回,但 A 从未收到该消息。这会导致软件显示为冻结状态。
我已经想出了一些可以接受的解决方法。我很想知道为什么 Windows 会丢失这些消息以及为什么这种情况只发生在一台计算机上。
这是代码的相关部分。
// 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;
解决方案
是 FCount
也受到保护 FQueueLock
?如果没有,那么你的问题在于 FCount
在处理完发布的消息后递增。
以下是可能发生的情况:
- B进入临界区
- B 打电话
PostMessage
- A 收到消息但没有执行任何操作
FCount
是0
- B 增量
FCount
- B 离开临界区
- A像鸭子一样坐在那里
一个快速的补救措施是增加 FCount
打电话之前 PostMessage
.
请记住,事情发生的速度可能比人们预期的要快(即。使用 PostMessage 发布的消息会被另一个线程捕获并处理,之后您才有机会增加 FCount 几行),特别是当您处于真正的多线程环境(多个 CPU)中时。这就是为什么我之前问“问题机器”是否有多个 CPU/核心。
解决此类问题的一个简单方法是使用附加日志记录来构建代码,以便在每次输入方法、进入/离开关键部分等时进行记录。然后您可以分析日志以查看事件的真实顺序。
另一方面,在像这样的生产者/消费者场景中可以完成的一个不错的小优化是使用两个队列而不是一个队列。当消费者醒来处理完整队列时,您可以将完整队列与空队列交换,然后仅锁定/处理完整队列,同时可以填充新的空队列,而无需两个线程尝试锁定彼此的队列。不过,您仍然需要在两个队列的交换中进行一些锁定。
其他提示
如果队列为空时,排队 调用函数时,函数将 使用PostMessage的告诉,有 是数据在队列中。
您锁定检查队列大小并发出PostMessage
之前的消息队列?您可能会遇到在您检查队列,并发现它非空的时候,其实A被处理的最后消息,并即将进入空闲状态的竞争条件。
要看到,如果你实际上遇到了竞争条件,而不是与PostMessage
一个问题的时候,你可以切换到使用事件。工作线程(A)将等待该事件,而不是等待消息。 B将简单地设置该事件,而不是发布的消息。
这行之有效相当长一段时间 直到一个特定的计算机开始 失去偶尔消息。
以任何机会,并CPU或这个特定的计算机有比其他人不同的内核数量,你看看有没有问题?有时,当你从单CPU的机器切换到一台具有多于一个的物理CPU /核心,新竞争条件或死锁可能出现。
难道还有一个第二个实例在不知不觉中运行和饮食中的消息,将它们标记为处理?