PDA

Ver la Versión Completa : Procesos largos, Threads y progreso


manuel_conde
18-11-2005, 21:21:48
Hola a todos. Hoy os propongo un desafío que llevo tiempo encima de él.
El caso es que tengo unos eventos que hacen mogollón de cosas.
Por ejemplo, le doy a un botón de "Actualizar" y el evento hace cosas que pueden llevar unos veinte segundos. Entonces, mientras se realiza el proceso, me gustaría abrir un formulario con una label a modo de contador que fuese indicando el tiempo va transcurriendo.
Se me ocurrió lanzar un thread al inicio de la gestión del evento que abra un formulario y vaya actualizando la label, y matar el thread al final del proceso, pero ... oohhhh, el thread no hace nada hasta que termina todo el proceso.
Me gustaría saber si alguien ha resuelto algo así...
Por ejemplo:
procedure TForm1.Button1Click(Sender: TObject);
begin
th := tmythread.create(false); //creo el thread
// Aquí va un proceso que le lleva 20 segs....
// supongamos que en vez de llevarle 20 segs le lleva mucho, mucho tiempo:
while true do begin end;

th.terminate; // terminamos el thread
end;

Mientras tanto... el thread estaría haciendo:
procedure TmyThread.Execute;
begin
while not fin do
Synchronize(UpdateCaption)
end;

procedure TmyThread.UpdateCaption;
begin
Form1.Caption := floattostr(now);;
end;


---------------------------------------------
Lo que he visto es que el thread no empieza a funcionar hasta que termine el evento desde el cual se inició la ejecución del evento (y claro... empieza a funcionar caundo ya no hace falta).

Gracias a todos.

dec
19-11-2005, 02:59:40
Hola,

He estado haciendo unas pruebas y lo que parece que puede ocurrir es que el hilo principal de la aplicación impida que el hilo que tú creas trabaje. Si dentro del proceso que te lleva tanto tiempo incluyeras un "Application.ProcessMessages;" verías cómo el hilo que creas realiza su función. De todos modos, no creo que fuera una buena idea.

Yo lo que pienso es que acaso no te sea necesaria la utilización de ningún hilo aparte del principal, y, mucho menos, utilizar ese hilo únicamente para mantener al usuario informado de cierto proceso que llevas a cabo... en el hilo principal.


Piensa que una aplicación no funciona mejor porque haga uso de dos o más hilos; es posible que sea al contrario. Los dos hilos no se ejecutarán en paralelo, de tal forma que cuando uno de ellos se ejecute el otro estará esperando "su turno", esperándose mutuamente de forma que acaso la tarea por esto mismo se retarde más de la cuenta.

Según he leído un proceso de diez segundos no implica la necesidad de mostrar cierto mensaje al usuario informándole del mismo: bastaría con el cambio del cursor del ratón: con el reloj de arena el usuario sabría que algo está llevándose a cabo.

Tratándose de veinte segundos, tal vez con un mensaje informativo que contuviera una imagen animada indicando que se está llevando a cabo una tarea fuera más que suficiente. Y, en todo caso, si es posible "medir" el proceso y mostrarle al usuario cierta información sobre su progreso,... en tu caso creo que podrías hacerlo en el mismo proceso. Si se trata de un bucle, por ejemplo, una de las instrucciones que incluya puede ser precisamente la que actualize determinada "Caption", "ProgressBar", etc.

En definitiva. El problema que te encuentras creo que estriba en que tratas de hacer uso de un dos hilos de ejecución cuando acaso no son necesarios, de tal forma que, efectivamente, la posible solución te ocasiona encima quebraderos de cabeza. En fin. No sé si todo este rollo te servirá de algo o no. ;)

dec
19-11-2005, 03:04:05
Hola de nuevo,

Se me han pasado por alto un par de cuestiones. Primeramente darte la bienvenida a estos Foros y recomendarte (si me lo permites) la lectura de su guía de estilo (http://www.clubdelphi.com/foros/guiaestilo.php). En segundo lugar te propongo una prueba. A la siguiente línea:


th := tmythread.create(false); //creo el thread

Añade esta otra:


th.Priority := tpHighest; // Prioridad para el thread

De tal forma que "la cosa" quede en:


th := tmythread.create(false); //creo el thread
th.Priority := tpHighest; // Prioridad para el thread

Si tienes un ordenador/computador más o menos como el mío podrás ver cómo ahora no es el hilo principal el que inutiliza al hilo que tú creas, sino al contrario: es el hilo que tú creas el que inutiliza al hilo principal... cosas de los Hilos. ;)

jachguate
19-11-2005, 03:48:19
Hola.

Aqui el problema del "congelamiento" del segundo hilo está en la llamada a Synchronize:

Synchronize causes the call specified by Method to be executed using the main thread, thereby avoiding multi-thread conflicts. If you are unsure whether a method call is thread-safe, call it from within the Synchronize method to ensure that it executes in the main thread.

El punto es que para actualizar la etiqueta no hay de otra... debe hacerse en el hilo principal o habrá problemas.

Me parece mas lógico crear un segundo hilo para realizar el proceso en cuestión, mientras el hilo principal de la aplicación se queda en un ciclo informando al usuario del avance. Así, todo lo relacionado con la interfaz visual permanece en el hilo principal y se evita por completo el uso de Synchronize.

Hasta luego.

;)

manuel_conde
19-11-2005, 14:23:07
Agradezco vuestras rápidas contestaciones.
He estado probando lo que me habeis recomendado:
1.- El cambio de la prioridad a tpHighest del nuevo thread no ha dado resultado, pues sigue congelado (incluso con time critical).
2.-La alternativa dada por jachguate tampoco me satisface del todo, porque lo que realmente tengo no es un proceso que tarde mucho, sino muchos procesos distintos. Aunque probaré la opción de crear un thread y pasarle la dirección del proceso a realizar (lo probaré hoy por la tarde y ya os comentaré), llevando a cabo el informe de estado en el thread principal.
3.- Probaré también la opción de cambiar el cursor del ratón, a ver qué tal.

Finalmente, he estado leyendo la guia de estilo (claro que te permito la recomendación).

Muchas gracias a todos. Nunca pensé tener respuestas tan rápido.
Un saludo a todos.

manuel_conde
19-11-2005, 15:01:40
Hola de nuevo a todos.

Ya he probado una alternativa: he creado un thread al cual se le puede pasar la dirección del trabajo a realizar, dejando para el proceso principal el trabajo de indicar el estado de progreso. Ahí va la solución:


Proceso principal:

type
TForm1 = class(TForm)
Button1: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
private
public
procedure ProcesoLargo1(datos : integer);
procedure ProcesoLargo2(datos : integer);
end;
var
Form1: TForm1;
implementation
uses unit2;
{$R *.dfm}
procedure tform1.ProcesoLargo1(datos : integer);
begin
// un proceso largo, y que tiene interacción con elementos
// visuales.
sleep(10000);
button1.Caption := inttostr(datos);
sleep(10000);
end;
procedure tform1.ProcesoLargo2(datos : integer);
begin
// un proceso largo, y que tiene interacción con elementos
// visuales.
sleep(10000);
button1.Caption := inttostr(datos);
sleep(10000);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
th : tmythread;
begin
th := tmythread.create(true);
th.ExecTrabajo(form1.ProcesoLargo1,1234);
while th.trabajando do
begin
application.ProcessMessages;
form1.Caption := 'Trabajando ' +floattostr(now);
end;
form1.Caption := 'Trabajo finalizado';
freeAndNil(th);
end;

Proceso del thread:

type
TCProcedure = procedure (datos: integer) of object; // Procedure in a class
tmythread = class(TThread)
public
trabajando : boolean;
procedure ExecTrabajo(proc : TcProcedure; losDatos: integer);
protected
datos : integer;
proceso : TcProcedure;
procedure Execute; override;
end;
implementation
procedure tmythread.ExecTrabajo(proc : TcProcedure; losDatos: integer);
begin
trabajando := true;
proceso := proc;
datos := losDatos;
resume;
end;
procedure tmythread.Execute;
begin
proceso(datos); // ejecuta el procedimiento de la otra clase
trabajando :=false;
end;


Seguiremos investigando....