Pergunta

Eu escrevi um aplicativo do Windows multi-threaded, onde rosca:
A - é um Windows formar essa interação alças usuário e processar os dados de B.
B - ocasionalmente gera dados e passa duas A.

Um fio fila de seguro é usado para passar os dados do segmento B para A. A enqueue e dequeue funções são guardados usando um Windows objetos de seção crítica.

Se a fila está vazia quando a função enqueue é chamado, a função vai usar PostMessage para contar uma que há dados na fila. A função verifica se a chamada para PostMessage é executado com sucesso e repetidamente chama PostMessage se não for bem sucedida (PostMessage ainda tem de falhar).

Isso funcionou bem por algum tempo até que um computador específico começou a perder a mensagem ocasional. Ao perder, quero dizer que, retornos postMessage com sucesso em B, mas um nunca recebe a mensagem. Isso faz com que o software para aparecer congelado.

Eu já venha com um par de soluções alternativas aceitáveis. Estou interessante em saber por que o Windows está perdendo estas mensagens e porque esta é apenas a acontecer por um computador.

Aqui está as partes relevantes do código.

// 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;
Foi útil?

Solução

É FCount também protegido por FQueueLock? Se não, então suas mentiras problema com FCount sendo incrementado após a mensagem postada já é processado.

Aqui está o que pode estar acontecendo:

  1. B entra seção crítica
  2. B chama PostMessage
  3. A recebe a mensagem, mas não faz nada desde FCount é 0
  4. incrementos B FCount
  5. folhas B seção crítica
  6. A senta lá como um pato

Um remédio rápido seria FCount incremento antes de chamar PostMessage.

Tenha em mente que as coisas podem acontecer mais rápido do que seria de esperar (ou seja, a mensagem postada com PostMessage ser capturado e processado por outro segmento antes de você ter a chance de incrementar função FCOUNT algumas linhas depois), especialmente quando você está em um verdadeiro ambiente com rosca de multi (várias CPUs). É por isso que eu pedi mais cedo se a "máquina problema" tinha múltiplas CPUs / núcleos.

Uma maneira fácil de Solucionar problemas como estes é scaffold o código com registro adicional a registrar cada vez que você entra um método, entrar / sair de uma seção crítica etc. Então você pode analisar o log para ver a verdadeira ordem dos eventos.

Em uma nota separada, um pouco de otimização de bom que pode ser feito em um cenário de produtor / consumidor como este é usar duas filas em vez de um. Quando o consumidor acorda para processar a fila cheia, você trocar a fila completa com um vazio e apenas lock / processar a fila cheia enquanto a nova fila vazia pode ser preenchida sem os dois tópicos que tentam bloquear as filas do outro. Você ainda precisa de algum bloqueio na troca das duas filas embora.

Outras dicas

Se a fila está vazia quando o enfileiramento função é chamada, a função usar PostMessage para contar uma que não são dados na fila.

Você está bloqueando a fila de mensagens antes de verificar o tamanho da fila e emitindo o PostMessage? Você pode estar sofrendo de uma condição de corrida onde você verifique a fila e achar que é não vazio, quando na verdade Um está processando a última mensagem e está prestes a ir ocioso.

Para ver se você está na verdade enfrentando uma condição de corrida e não um problema com PostMessage, você poderia passar a usar um evento. O segmento de trabalho (A) iria esperar no evento, em vez de esperar por uma mensagem. B seria simplesmente definir esse evento em vez de postar uma mensagem.

Isso funcionou bem por algum tempo até que um computador específico começou a perder a mensagem ocasional.

Por acaso, não o número de CPUs ou núcleos que este computador específico tem diferente do que os outros onde você vê nenhum problema? Às vezes, quando você mudar de uma máquina com uma única CPU de uma máquina com mais de um físico CPU / core, novas condições de corrida ou impasses possam surgir.

Poderia haver uma segunda instância, sem saber, correndo e comendo as mensagens, marcá-los como tratado?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top