Deadlock in chiusura filetto
-
23-08-2019 - |
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?
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.