Blocage lorsque la fermeture de fil
-
23-08-2019 - |
Question
J'ai créé une classe qui ouvre un port COM et les poignées se chevauchaient lecture et d'écriture. Il contient deux threads indépendants - un qui lit et qui écrit des données. Les deux d'entre eux appellent des procédures OnXXX (par exemple OnRead ou OnWrite) informer sur le fonctionnement lecture ou d'écriture terminée.
Ce qui suit est un court exemple de l'idée comment les discussions de travail:
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;
Quand vous regardez la procédure Close (), vous verrez qu'il pénètre dans la section critique, arrête le thread d'écriture et attend qu'elle se termine. En raison du fait que le fil d'écriture peut enqueue une nouvelle valeur à écrire quand il appelle la méthode OnWrite, il va essayer d'entrer dans la même section critique lors de l'appel de la procédure Write () de la classe TAsyncSerialPort.
Et nous avons une impasse. Le fil qui a appelé la méthode Close () est entré dans la section critique, puis attend que le fil d'écriture pour être fermé, tandis que dans le même temps que thread attend la section critique à être libérés.
J'ai pensé pendant un temps assez long et je ne l'ai pas réussi à trouver une solution à ce problème. La chose est que je voudrais être sûr que pas de lecture / d'écriture fil est en vie lorsque la méthode Close () est à gauche, ce qui signifie que je ne peux pas mettre le drapeau Terminated de ces fils et laisser.
Comment puis-je résoudre le problème? Peut-être que je devrais changer mon approche de manipulation port série de manière asynchrone?
Merci pour vos conseils à l'avance.
Mariusz.
--------- ---------- EDIT
Que diriez-vous d'une telle solution?
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;
Si ma pensée est correcte, cela devrait éliminer le problème de blocage. Malheureusement, cependant, je ferme gérer le port de communication avant que le thread d'écriture est fermé. Cela signifie que lorsqu'il appelle une méthode qui prend le port comm poignée comme l'un de ses arguments (par exemple, écrire, lire, WaitCommEvent) devrait être soulevé une exception dans ce thread. Puis-je être sûr que si je prends cette exception dans ce fil, il ne sera pas affecter le travail de l'ensemble de l'application? Cette question peut paraître stupide, mais je pense que certaines exceptions peuvent provoquer le système d'exploitation pour fermer l'application qui a causé, non? Dois-je à vous soucier que dans ce cas?
La solution
Oui, vous devriez probablement revoir votre approche. Les opérations asynchrones sont disponibles exactement pour éliminer la nécessité pour les fils. Si vous utilisez les threads, utilisez alors des appels synchrones (blocage). Si vous utilisez des opérations asynchrones, puis tout gérer dans un thread -. Pas nécessairement le fil conducteur, mais il n'a pas de sens de l'OMI à faire l'envoi et la réception dans différents threads
Il y a des façons de cours autour de votre problème de synchronisation, mais je préfère changer la conception.
Autres conseils
Vous pouvez prendre le lock-out à la fermeture. Au moment où il revient de la WaitFor, le corps de fil a remarqué qu'il a été mis fin, a terminé la dernière boucle, et terminée.
Si vous ne vous sentez pas heureux de faire cela, alors vous pouvez passer d'un réglage de la serrure juste avant la FreeAndNil. Cela permet explicitement les mécanismes d'arrêt de fil fonctionnent avant d'appliquer le verrou (donc il ne sera pas en concurrence avec quoi que ce soit pour la serrure)
EDIT:
(1) Si vous voulez aussi fermer les comms gérer le faire après la boucle dans l'exécution, ou dans le destructor du fil.
(2) Désolé, mais votre solution éditée est un terrible gâchis. Et Terminate WaitFor fera tout ce dont vous avez besoin, parfaitement en toute sécurité.
Le principal problème semble être que vous placez tout le contenu de proximité dans une section critique. Je suis presque sûr (mais vous devrez vérifier la documentation) qui TThread.Terminate et TThread.WaitFor sont sûrs de faire appel à l'extérieur de la section. En tirant cette partie en dehors de la section critique vous permettra de résoudre l'impasse.