PostMessage偶尔会丢失一条消息

时间:2009-01-14 21:35:58

标签: windows multithreading synchronization messaging race-condition

我写了一个多线程的windows应用程序,其中thread:
   A - 是一个窗体,用于处理用户交互并处理来自B的数据    B - 偶尔生成数据并将其传递两个A.

线程安全队列用于将数据从线程B传递到A.使用Windows关键节对象保护入队和出队函数。

如果在调用enqueue函数时队列为空,则该函数将使用PostMessage告诉A队列中有数据。该函数检查以确保成功执行对PostMessage的调用,并在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;

3 个答案:

答案 0 :(得分:3)

FCount是否也受FQueueLock保护?如果没有,那么问题在于FCount在已发布消息已经处理后递增。

以下是可能发生的事情:

  1. B进入关键部分
  2. B来电PostMessage
  3. A收到邮件,但由于FCount0
  4. ,因此无法执行任何操作
  5. B递增FCount
  6. B离开关键部分
  7. A像鸭子一样坐在那里
  8. 快速补救措施是在致电FCount之前增加PostMessage

    请记住,事情可能比预期更快发生(即PostMessage发布的消息被其他线程捕获并处理,之后您有机会在几行之后增加FCount),尤其是当您处于真正的多线程环境(多个CPU)。这就是为什么我之前问过“问题机器”是否有多个CPU /核心。

    解决这些问题的一种简单方法是,每次输入方法,输入/离开关键部分等时,都需要使用附加日志记录来编写代码,然后您可以分析日志以查看事件的真实顺序。

    另外,在这样的生产者/消费者场景中可以完成的一个很好的小优化就是使用两个队列而不是一个队列。当消费者醒来处理完整队列时,您将整个队列与空队列交换,只需锁定/处理完整队列,同时可以填充新的空队列,而不会让两个线程试图锁定彼此的队列。但是,在交换两个队列时你仍然需要一些锁定。

答案 1 :(得分:1)

  

如果排队时队列为空   调用函数,函数会   使用PostMessage告诉A那里   是队列中的数据。

在检查队列大小并发出PostMessage之前,您是否锁定了消息队列?您可能遇到竞争状况,在这种情况下您检查队列并发现它非空,而实际上A正在处理最后一条消息并且即将闲置。

要查看您是否确实遇到了竞争条件且PostMessage没有问题,您可以切换到使用某个事件。工作线程(A)将等待事件而不是等待消息。 B只会设置该事件而不是发布消息。

  

这种方法运作了很长时间   直到一台特定的计算机开始   失去偶尔的消息。

任何机会,这台特定计算机的CPU或核心数量是否与您认为没有问题的其他计算机不同?有时,当您从单CPU机器切换到具有多个物理CPU /核心的机器时,可能会出现新的竞争条件或死锁。

答案 2 :(得分:-1)

是否有第二个实例在不知不觉中正在运行并吃掉消息,将其标记为已处理?