Domanda

ho creato una classe che apre una porta COM e le maniglie sovrapposto lettura e scrittura. Contiene due thread indipendenti - uno che legge e uno che scrive i dati. Entrambi chiamano procedure onxxx (es OnRead o OnWrite) la notifica di operazione di lettura o scrittura finito.

Il seguente è un breve esempio dell'idea come funzionano i fili:

  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 si guarda la procedura di Close (), vedrete che entra nella sezione critica, termina il thread di scrittura e quindi attende che finisca. A causa del fatto che il thread di scrittura può accodare un nuovo valore da scrivere quando si chiama il metodo OnWrite, cercherà di entrare nella stessa sezione critica quando si chiama la procedura di scrittura () della classe TAsyncSerialPort.

E qui abbiamo una situazione di stallo. Il filo che chiama il metodo Close () è entrato nella sezione critica e quindi attende il filo scrittura da chiudere, mentre allo stesso tempo che thread attende per la sezione critica per essere liberati.

Ci ho pensato per un periodo piuttosto lungo e non sono riuscito a trovare una soluzione a questo problema. Il fatto è che mi piacerebbe essere sicuri che nessun filo di lettura / scrittura è vivo quando il metodo Close () è a sinistra, il che significa che non posso solo impostare il flag terminato di quei fili e lasciare.

Come posso risolvere il problema? Forse dovrei cambiare il mio approccio alla gestione porta seriale asincrona?

Grazie per i vostri consigli in anticipo.

Mariusz.

--------- ---------- EDIT
Che ne dite di una soluzione del genere?

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 il mio pensiero è corretto, questo dovrebbe eliminare il problema deadlock. Purtroppo, però, chiudo maniglia della porta di comunicazione prima che il filo scrittura è chiuso. Ciò significa che quando chiama qualsiasi metodo che accetta la maniglia porta di comunicazione come uno dei suoi argomenti (es scrittura, lettura, WaitCommEvent) un'eccezione dovrebbe essere sollevata in tale filo. Posso essere sicuro che se prendo tale eccezione in quel filo non influenzerà il lavoro di tutta l'applicazione? Questa domanda può sembrare stupido, ma credo che alcune eccezioni possono causare il sistema operativo per chiudere l'applicazione che ha causato, giusto? Devo preoccuparsi che in questo caso?

È stato utile?

Soluzione

Si, probabilmente si dovrebbe riconsiderare il vostro approccio. operazioni asincrone sono disponibili esattamente per eliminare la necessità di fili. Se si utilizzano le discussioni, quindi utilizzare le chiamate sincrone (blocco). Se si utilizza operazioni asincrone, quindi gestire tutto in un unico filo -. Non necessariamente il thread principale, ma non ha senso IMO per fare l'invio e la ricezione in diversi thread

Ci sono modi naturalmente intorno alla vostra problema di sincronizzazione, ma preferirei cambiare il design.

Altri suggerimenti

Si può prendere il blocco fuori dalla Chiudi. Con il tempo che ritorna dalla WaitFor, il corpo thread è notato è stata terminata, ha completato l'ultimo ciclo, e si è conclusa.

Se non ti senti felice di fare questo, allora si potrebbe spostare l'impostazione del blocco poco prima della FreeAndNil. Ciò consente esplicitamente i meccanismi di arresto filo funzionano prima di applicare il blocco (in modo da non dover competere con qualsiasi cosa per il blocco)

EDIT:

(1) Se anche voi volete chiudere le comunicazioni gestire farlo dopo il ciclo in esecuzione, o distruttore del thread.

(2) Ci dispiace, ma la soluzione è a cura di un terribile disastro. Terminate e WAITFOR farà tutto il necessario, perfettamente in sicurezza.

Il problema principale sembra essere che si inserisce l'intero contenuto di chiudere in una sezione critica. Sono quasi sicuro (ma dovrete controllare la documentazione), che TThread.Terminate e TThread.WaitFor sono sicuro chiamare dall'esterno della sezione. Tirando la parte al di fuori della sezione critica si risolverà la situazione di stallo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top