Interbloqueo cuando filete de cierre de
-
23-08-2019 - |
Pregunta
He creado una clase que abre un puerto COM y mangos superpuesto lectura y escritura. Contiene dos hilos independientes - uno que lee y que escribe datos. Ambos se llaman procedimientos OnXxx (por ejemplo OnRead o OnWrite) notificar acerca de la operación de lectura o escritura terminado.
El siguiente es un breve ejemplo de la idea de cómo funcionan los hilos:
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;
Cuando nos fijamos en el procedimiento Close (), verá que entra en la sección crítica, termina el hilo por escrito y luego espera a que termine. Debido al hecho de que el hilo de la escritura puede poner en cola un nuevo valor a escribir cuando se llama al método OnWrite, que tratará de introducir la misma sección crítica cuando se llama al procedimiento Write () de la clase TAsyncSerialPort.
Y aquí tenemos un punto muerto. El hilo que llama al método Close () entró en la sección crítica y espera a que el hilo de la escritura a ser cerrada, mientras que al mismo tiempo que hilo espera para la sección crítica para ser liberados.
He estado pensando desde hace mucho tiempo y no me las arreglo para encontrar una solución a ese problema. La cosa es que me gustaría estar seguro de que ningún hilo de lectura / escritura está vivo cuando se deja el método Close (), lo que significa que no puedo acaba de establecer el indicador Terminado de esos temas y dejar.
¿Cómo puedo solucionar el problema? Tal vez debería cambiar mi enfoque para el manejo de puerto serie asíncrona?
Gracias por su consejo de antemano.
Mariusz.
--------- ---------- EDITAR
¿Qué tal una solución de este tipo?
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 mi pensamiento es correcto, esto debería eliminar el problema de estancamiento. Desafortunadamente, sin embargo, cierro el puerto de comunicaciones manejar antes de que el hilo de la escritura está cerrado. Esto significa que cuando se llama a cualquier método que toma el mango puerto de comunicaciones como uno de sus argumentos (por ejemplo, escribir, leer, WaitCommEvent) una excepción debe ser planteado en ese hilo. ¿Puedo estar seguro de que si cojo esta excepción en ese hilo que no afectará el trabajo de toda la aplicación? Esta pregunta puede sonar estúpido, pero creo que algunas excepciones pueden hacer que el sistema operativo para cerrar la aplicación que lo causó, ¿verdad? ¿Hay que preocuparse de que en este caso?
Solución
Sí, probablemente debería reconsiderar su enfoque. operaciones asincrónicas están disponibles exactamente a eliminar la necesidad de hilos. Si utiliza hilos, a continuación, utilizar las llamadas síncronas (bloqueo). Si utiliza las operaciones asincrónicas, y luego manejar todo en un único hilo -. No necesariamente el hilo principal, pero no tiene sentido de la OMI para hacer el envío y la recepción en diferentes hilos
Hay por supuesto maneras alrededor de su problema de sincronización, pero prefiero cambiar el diseño.
Otros consejos
Puede tomar la cerradura de la Cerca. En el momento en que vuelve de la WaitFor, el cuerpo de mensaje ha notado que se ha terminado, terminado el último bucle, y terminó.
Si usted no se siente feliz haciendo esto, entonces se podría mover ajustar el bloqueo justo antes de la FreeAndNil. Esto permite explícitamente los mecanismos de cierre de rosca funcionan antes de aplicar el bloqueo (por lo que no tendrá que competir con cualquier cosa por el bloqueo)
EDIT:
(1) Si también desea cerrar las comunicaciones manejan hacerlo después del bucle en el Ejecutar, o en el destructor de la rosca.
(2) Lo sentimos, pero su solución editado es un lío terrible. Esperar a terminar y va a hacer todo lo necesario, perfectamente segura.
El problema principal parece ser que coloque el contenido completo de Close en una sección crítica. Estoy casi seguro (pero tendrá que comprobar la documentación) que TThread.Terminate y TThread.WaitFor son seguros para llamar desde fuera de la sección. Tirando de la parte exterior de la sección crítica que va a resolver el punto muerto.