Pergunta

Eu criei uma classe que abre uma porta COM e alças sobrepostas operações de leitura e gravação. Ele contém dois tópicos independentes - um que lê e aquele que grava dados. Ambos chamar procedimentos onxxx (por exemplo OnRead ou OnWrite) notificando sobre leitura acabado ou operação de gravação.

O seguinte é um pequeno exemplo da idéia de como os tópicos funcionam:

  TOnWrite = procedure (Text: string);

  TWritingThread = class(TThread)
  strict private
    FOnWrite: TOnWrite;
    FWriteQueue: array of string;
    FSerialPort: TAsyncSerialPort;
  protected
    procedure Execute; override;
  public
    procedure Enqueue(Text: string);
    {...}
  end;

  TAsyncSerialPort = class
  private
    FCommPort: THandle;
    FWritingThread: TWritingThread;
    FLock: TCriticalSection;
    {...}
  public
    procedure Open();
    procedure Write(Text: string);
    procedure Close();
    {...}
  end;

var
  AsyncSerialPort: TAsyncSerialPort;

implementation

{$R *.dfm}

procedure OnWrite(Text: string);
begin
  {...}
  if {...} then
    AsyncSerialPort.Write('something');
  {...}
end;

{ TAsyncSerialPort }

procedure TAsyncSerialPort.Close;
begin
  FLock.Enter;
  try
    FWritingThread.Terminate;
    if FWritingThread.Suspended then
      FWritingThread.Resume;
    FWritingThread.WaitFor;
    FreeAndNil(FWritingThread);

    CloseHandle(FCommPort);
    FCommPort := 0;
  finally
    FLock.Leave;
  end;
end;

procedure TAsyncSerialPort.Open;
begin
  FLock.Enter;
  try
    {open comm port}
    {create writing thread}
  finally
    FLock.Leave;
  end;
end;

procedure TAsyncSerialPort.Write(Text: string);
begin
  FLock.Enter;
  try
    {add Text to the FWritingThread's queue}
    FWritingThread.Enqueue(Text);
  finally
    FLock.Leave;
  end;
end;

{ TWritingThread }

procedure TWritingThread.Execute;
begin
  while not Terminated do
  begin
    {GetMessage() - wait for a message informing about a new value in the queue}
    {pop a value from the queue}
    {write the value}
    {call OnWrite method}
  end;
end;

Quando você olha para o procedimento Close (), você vai ver que ele entra na seção crítica, termina o fio de escrita e espera que ela termine. Devido ao fato de que o fio de escrita pode enfileirar um novo valor a ser escrito quando ele chama o método OnWrite, ele vai tentar entrar na mesma seção crítica ao chamar o procedimento Write () da classe TAsyncSerialPort.

E aqui temos um impasse. O segmento que chama o método Close () entrou na seção crítica e aguarda o segmento de escrita para ser fechada, enquanto, ao mesmo tempo em que aguarda thread para a seção crítica para ser liberado.

Eu estive pensando por muito tempo e eu não conseguir encontrar uma solução para esse problema. A coisa é que eu gostaria de ter certeza de que nenhuma leitura / escrita fio está vivo quando o Close () método é para a esquerda, o que significa que eu não posso simplesmente definir o sinalizador Terminado desses tópicos e licença.

Como posso resolver o problema? Talvez eu devesse mudar minha abordagem para lidar com porta serial de forma assíncrona?

Obrigado por seu conselho com antecedência.

Mariusz.

--------- ---------- EDIT
Que tal solução tal?

procedure TAsyncSerialPort.Close;
var
  lThread: TThread;
begin
  FLock.Enter;
  try
    lThread := FWritingThread;
    if Assigned(lThread) then
    begin
      lThread.Terminate;
      if lThread.Suspended then
        lThread.Resume;
      FWritingThread := nil;
    end;

    if FCommPort <> 0 then
    begin
      CloseHandle(FCommPort);
      FCommPort := 0;
    end;
  finally
    FLock.Leave;
  end;

  if Assigned(lThread) then
  begin
    lThread.WaitFor;
    lThread.Free;
  end;
end;

Se o meu pensamento está correto, isso deve eliminar o problema de impasse. Infelizmente, no entanto, eu fechar o identificador de porta de comunicação antes que o segmento de escrita está fechado. Isto significa que quando ele chama qualquer método que leva a alça porta de comunicação como um de seus argumentos (por exemplo, escrever, ler, WaitCommEvent) uma exceção deve ser criado em um dos threads. Posso ter a certeza que se eu pegar essa exceção nesse segmento não afetará o trabalho de toda a aplicação? Esta pergunta pode parecer estúpido, mas eu acho que algumas exceções podem fazer com que o OS para fechar o aplicativo que causou isso, certo? Eu tenho que preocupar com isso neste caso?

Foi útil?

Solução

Sim, você provavelmente deve reconsiderar a sua abordagem. Operações assíncronas estão disponíveis exatamente para eliminar a necessidade de fios. Se você usar threads, em seguida, usar chamadas síncronas (bloqueio). Se você usar operações assíncronas, então lidar com tudo em um fio -. Não necessariamente o segmento principal, mas não faz sentido IMO para fazer o envio e recebimento em diferentes segmentos

Existem maneiras de curso em torno do seu problema de sincronização, mas eu prefiro mudar o projeto.

Outras dicas

Você pode tomar o bloqueio fora do Fechar. No momento em que ele retorna a partir do waitFor, o corpo discussão notado que tenha sido encerrado, concluído o último ciclo, e terminou.

Se você não se sentir feliz fazendo isso, então você pode mover-se ajustar o bloqueio pouco antes da FreeAndNil. Este explicitamente permite que os mecanismos de fio de desligamento trabalhar antes de aplicar o bloqueio (por isso não terá que competir com qualquer coisa para o bloqueio)

EDIT:

(1) Se você também quiser fechar as comms lidar fazê-lo após o loop em Executar, ou em processo de destruição do segmento.

(2) Lamentamos, mas a sua solução editado é uma terrível bagunça. Terminar e waitFor vai fazer tudo o que precisa, perfeitamente com segurança.

O principal problema parece ser que você colocar todo o conteúdo do Close em uma seção crítica. Tenho quase certeza (mas você terá que verificar os docs) que TThread.Terminate e TThread.WaitFor são seguros para chamar de fora da seção. Ao puxar essa parte fora da seção crítica você vai resolver o impasse.

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