代码之家  ›  专栏  ›  技术社区  ›  Lawrence Barsanti

PostMessage偶尔会丢失消息

  •  0
  • Lawrence Barsanti  · 技术社区  · 17 年前

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

    线程安全队列用于将数据从线程B传递到A。入队和出队功能使用窗口关键部分对象进行保护。

    如果调用enqueue函数时队列为空,则该函数将使用PostMessage告诉A队列中有数据。该函数检查以确保对PostMessage的调用成功执行,如果不成功,则重复调用PostMessage(PostMessage尚未失败)。

    这在相当长的一段时间内运行良好,直到一台特定的计算机开始丢失偶尔的消息。我所说的lose是指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 回复  |  直到 16 年前
        1
  •  3
  •   Ates Goral    17 年前

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

    以下是可能发生的情况:

    1. B进入临界区
    2. B呼叫 PostMessage
    3. A收到消息,但此后什么也没做 FCount 0
    4. B增量 FCount
    5. B离开临界区
    6. A像鸭子一样坐在那里

    一个快速的补救措施是增加 FCount 打电话之前 发送消息 .

    请记住,事情发生的速度可能比预期的要快(即,在您有机会在几行后增加FCount之前,用PostMessage发布的消息会被另一个线程捕获和处理),尤其是在您处于真正的多线程环境(多个CPU)中时。这就是为什么我早些时候问“问题机器”是否有多个CPU/内核。

    解决此类问题的一个简单方法是用额外的日志记录来构建代码,以便在每次输入方法、输入/离开关键部分等时进行日志记录。然后,您可以分析日志以查看事件的真实顺序。

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

        2
  •  1
  •   Ates Goral    17 年前

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

    在检查队列大小并发出 PostMessage ?您可能遇到了竞争情况,在检查队列时发现它不是空的,而实际上a正在处理最后一条消息并即将进入空闲状态。

    看看你是否真的遇到了种族问题,而不是 发送消息 ,您可以切换到使用事件。工作线程(A)将等待事件,而不是等待消息。B只会设置该事件,而不是发布消息。

    这在相当长的一段时间内效果很好 直到一台特定的计算机开始 丢失偶尔的消息。

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

        3
  •  -1
  •   scottm    17 年前

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