2010-08-24 18 views
9

Tengo un hilo principal y un hilo separado en mi programa. Si el hilo separado termina antes del hilo principal, debería liberarse automáticamente. Si el hilo principal termina primero, debería liberar el hilo separado.Libere un TThread de forma automática o manual

Sé de FreeOnTerminate, y he leído que debe tener cuidado al usarlo.

Mi pregunta es, ¿el siguiente código es correcto?

procedure TMyThread.Execute; 
begin 
    ... Do some processing 

    Synchronize(ThreadFinished); 

    if Terminated then exit; 

    FreeOnTerminate := true; 
end; 

procedure TMyThread.ThreadFinished; 
begin 
    MainForm.MyThreadReady := true; 
end; 

procedure TMainForm.Create; 
begin 
    MyThreadReady := false; 

    MyThread := TMyThread.Create(false); 
end; 

procedure TMainForm.Close; 
begin 
    if not MyThreadReady then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 
end; 

Respuesta

8

Se puede simplificar esto a:

procedure TMyThread.Execute; 
begin 
    // ... Do some processing 
end; 

procedure TMainForm.Create; 
begin 
    MyThread := TMyThread.Create(false); 
end; 

procedure TMainForm.Close; 
begin 
    if Assigned(MyThread) then 
    MyThread.Terminate; 
    MyThread.Free; 
end; 

Explicación:

  • De cualquier uso o libre FreeOnTerminate el hilo manualmente, pero nunca ambas cosas. La naturaleza asíncrona de la ejecución del subproceso significa que corre el riesgo de no liberar el hilo o (lo que es mucho peor) hacerlo dos veces. No hay riesgo en mantener el objeto de subproceso alrededor después de que haya finalizado la ejecución, y no hay ningún riesgo al llamar al Terminate() en un subproceso que ya ha finalizado.

  • No hay necesidad de sincronizar el acceso a un valor lógico que sólo se escribe de un hilo y se lee de otra. En el peor de los casos, obtienes el valor incorrecto, pero debido a la ejecución asincrónica que es un efecto espurio de todos modos. La sincronización solo es necesaria para datos que no se pueden leer o escribir de forma atómica. Y si necesita sincronizar, no use Synchronize() para ello.

  • No es necesario tener una variable similar a MyThreadReady, ya que puede usar WaitForSingleObject() para consultar el estado de un hilo. Pase MyThread.Handle como el primero y 0 como el segundo parámetro y compruebe si el resultado es WAIT_OBJECT_0 - si es así, su subproceso ha finalizado la ejecución.

BTW: No utilice el evento OnClose, utilice OnDestroy lugar. El primero no es necesariamente llamado, en cuyo caso el hilo podría continuar ejecutándose y mantener vivo el proceso.

+0

¡Hola! FreeOnTerminate solo no es una opción. Por otro lado, no quiero que el hilo acapare la memoria mientras se ejecuta el programa Principal. Estoy sincronizando el booleano porque creo que garantiza que se ejecutará antes de MainForm.Close o después de MainForm.Close. Por lo tanto, MyThread.Terminate solo se invocará si FreeOnTerminate es falso. ¿Estoy equivocado aquí? – Steve

+0

¿Cuánta memoria tiene el hilo 'hog' una vez que ha terminado? Si no puedes decir que no tienes motivo para preocuparte. Mida primero. Pero si insistes, publica un mensaje de tu hilo como último y libera el hilo en el manejador de mensajes. 'Synchronize()' es demasiado vil como para siquiera pensar si tu código funcionaría bajo cualquier circunstancia. Solo dile no a eso. – mghie

+0

Aceptando esto como la solución ya que este es el más limpio. Gracias a todos por las respuestas! – Steve

2

No, su código no es buena (aunque probablemente funcionará en 99,99% o incluso el 100% de los casos). Si planea terminar el hilo de trabajo desde el hilo principal, no configure FreeOnTerminate en True (no veo lo que está tratando de ganar en el código anterior al configurar FreeOnTerminate en True, al menos hace que su código sea menos comprensible) .

Una situación más importante con la terminación de subprocesos de trabajo es que está intentando cerrar una aplicación mientras el subproceso de trabajo está en estado de espera. El hilo no se activará si solo llamas a Terminate, generalmente deberías usar un objeto de sincronización adicional (generalmente evento) para activar el hilo de trabajo.

Y una observación más - no hay necesidad de

begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 

si nos fijamos en el código TThread.Destroy, llama a terminar y WaitFor, por lo

MyThread.Free; 

es suficiente (al menos en Delphi 2009, no tiene fuentes de Delphi 7 para verificar).


Actualizado

Leer mghie respuesta. Considere la siguiente situación (mejor en 1 sistema de la CPU):

hilo principal está ejecutando

procedure TMainForm.Close; 
begin 
    if not MyThreadReady then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    MyThread.Free; 
    end; 
end; 

lo revisen valor MyThreadReady (que es falso) y se apagó por el programador.

Ahora el planificador cambia a hilo de trabajo; ejecuta

Synchronize(ThreadFinished); 

y obliga al programador a volver al hilo principal. El hilo principal continúa la ejecución:

MyThread.Terminate; // no problem 
    MyThread.WaitFor;  // ??? 
    MyThread.Free; 

¿Puede decir lo que sucederá en WaitFor? No puedo (requiere una mirada más profunda en TThread fuentes para responder, pero a primera vista parece un punto muerto).

Su error real es algo diferente: ha escrito un código no confiable e intenta averiguar si es correcto o no. Esa es una mala práctica con los hilos; en su lugar, debes aprender a escribir un código confiable.

En cuanto a recursos: cuando finaliza el TThread (con FreeOnTerminate = False) los únicos recursos que quedan asignados es el identificador de subproceso de Windows (no utiliza recursos importantes de Windows después de que finaliza el subproceso) y el objeto Delphi TThread en la memoria. No es un gran costo estar seguro.

+0

Si no configuro FreeOnTerminate en verdadero, el hilo no se liberará hasta que termine el hilo principal. Pueden transcurrir minutos o incluso horas hasta que se liberen los recursos del hilo separado. No quiero eso. No entiendo lo que quieres decir en el segundo párrafo de tu respuesta. ¿En qué caso no funcionará el código anterior? ¿Qué es ese 00.01%? – Steve

4

Hacer que el hilo principal asignar al gestor de eventos OnTerminate del subproceso de trabajo. Si el subproceso de trabajo termina primero, el controlador puede señalar el hilo principal para liberar el hilo. Si el hilo principal termina primero, puede terminar el hilo del trabajador. Por ejemplo:

procedure TMyThread.Execute; 
begin 
    ... Do some processing ... 
end; 

procedure TMainForm.Create; 
begin 
    MyThread := TMyThread.Create(True); 
    MyThread.OnTerminate := ThreadFinished; 
    MyThread.Resume; // or MyThread.Start; in D2010+ 
end; 

const 
    APPWM_FREE_THREAD = WM_APP+1; 

procedure TMainForm.ThreadFinished(Sender: TObject); 
begin 
    PostMessage(Handle, APPWM_FREE_THREAD, 0, 0); 
end; 

procedure TMainForm.WndProc(var Message: TMessage); 
begin 
    if Message.Msg = APPWM_FREE_THREAD then 
    StopWorkerThread 
    else 
    inherited; 
end; 

procedure TMainForm.StopWorkerThread; 
begin 
    if MyThread <> nil then 
    begin 
    MyThread.Terminate; 
    MyThread.WaitFor; 
    FreeAndNil(MyThread); 
    end; 
end; 

procedure TMainForm.Close; 
begin 
    StopWorkerThread; 
end; 
+0

No me gusta. En lugar de eliminar el problema, lo está ocultando más profundamente, por lo que cada vez es más difícil averiguar dónde puede fallar el código. – kludg

+2

No, no estoy ocultando nada. Esta es una forma mucho más eficiente de resolver el problema original, y funciona muy bien. El hilo de trabajo no debería intentar liberarse directamente. Como el hilo principal es el que inicia el hilo y necesita administrar el hilo de todos modos, debería ser el que libere el hilo. TThread tiene un evento OnTerminate específicamente con el propósito de notificar a otro código cuando termina el hilo, y logra EXACTAMENTE lo mismo que la llamada original 'Synchronize (ThreadFinished)' estaba haciendo ... ... –

+2

... The PostMessage () el truco consiste simplemente en retrasar la liberación real del hilo unos pocos milisegundos, ya que no se puede realizar dentro del evento OnTerminate. Pero el hilo de trabajo todavía se libera automáticamente cuando termina, simplemente no se libera desde el propio hilo, esa es la única diferencia. –

0

Honestamente, su


... Do some processing 

es el problema real aquí. ¿Es eso un lazo para hacer algo recursivamente? Si no es así y, en cambio, esa es una gran tarea, se debe considerar dividir esta tarea en pequeños procedimientos/funciones, y poner todos juntos en el cuerpo ejecutar, llamando a uno tras otro con condicional si es conocer el estado de rosca, como:



While not Terminated do 
begin 

    if MyThreadReady then 
    DoStepOneToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?); 

    if MyThreadReady then 
    DoStepTwoToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?, StepOne); 

    if MyThreadReady then 
    DoStepThreeToTaskCompletion 
    else 
    clean_and_or_rollback(Something Initialized?, StepOne, StepTwo); 

    Self.DoTerminate; // Not sure what to expect from that one 
end; 

Es sucio, casi un truco, pero funcionará como se esperaba.

Sobre FreeOnTerminate, así ... simplemente retire la declaración y siempre


FreeAndNil(ThreadObject); 

No soy un fan de sincronicen. Me gustan más las secciones críticas, por la flexibilidad de extender el código para manejar más datos compartidos.

En la sección pública forma, declaran:

ControlSection : TRTLCriticalSection; 

En forma crear o en otro lugar antes de hilo.Crear,

InitializeCriticalSection(ControlSection); 

Entonces, cada vez que se escribe en un recurso compartido (incluyendo la variable de MyThreadReady), hacen


EnterCriticalSection (ControlSection); 
    MyThreadReady := True; //or false, or whatever else 
LeaveCriticalSection (ControlSection); 

Antes de ir (salida), llaman


DeleteCriticalSection (ControlSection); 

y libera tu hilo como siempre lo haces.

Saludos Rafael

0

Me gustaría decir que simplemente no se recomienda mezclar modelos. O bien usa FreeOnTerminate y nunca vuelve a tocar el hilo, o no lo hace. De lo contrario, necesita una forma protegida para que los dos se comuniquen.

Dado que desea un control fino sobre la variable de subproceso, no utilice FreeOnTerminate. Si el hilo termina temprano, borre los recursos locales que el hilo ha consumido como lo haría normalmente, y luego simplemente deje que el hilo principal libere el hilo secundario cuando la aplicación finalice. Obtendrá lo mejor de ambos mundos: recursos liberados por el hilo hijo tan pronto como sea posible, y sin preocuparse por la sincronización del hilo. (Y tiene la ventaja adicional de ser mucho más simple en diseño/código/comprensión/soporte ...)

Cuestiones relacionadas