Ir al contenido


Foto

Controlar varios Threads


  • Por favor identifícate para responder
7 respuestas en este tema

#1 BlackDaemon

BlackDaemon

    Member

  • Miembros
  • PipPip
  • 22 mensajes

Escrito 02 octubre 2009 - 12:40

buenas tardes

A ver, lo que quiero hacer lo pondré con un ejemplo.

Tengo mi web donde en un directorio tengo varios ficheros (100)
entonces quiero descargar estos ficheros a mi pc, vale, esto lo hago con las indy, hasta aquí todo correcto a excepción de que se congela cuando está bajando uno y otro fichero, entonces la solución ? Threads..

Usé un thread para este trabajo, resultó todo bien como quería.. mando a hacer al thread todo el trabajo pesado y la app sin cogelarse ni nada, como la seda..
Peeeero ahora se me a ocurrido bajarme estos ficheros en varios threads (la cantidad configurable por el usuario)
Supongamos que el ususario pone 10 threads, entonces la carga se dividirá entre los 10 threads, osea, cada threads bajará 10 ficheros y los 10 threads estarán bajando simultaneamente.

Esto igual lo he logrado de casi igual manera que cuando tenía un solo thread.

Cuando tenía un solo thread:





delphi
  1.     ThreadDownload := TThreadDownload.Create(Memo1, 10);




Y ahora si quiero crear 10:



delphi
  1.   for i := 0 to 9 do
  2.   begin
  3.     Memo1.Lines.Append('Lanzamos thread # ' + IntToStr(i));
  4.     ThreadDownload := TThreadDownload.Create(Memo1, 10);
  5.     Memo1.Lines.Append('ThreadID: ' + IntToStr(ThreadDownload.ThreadID));
  6.     Memo1.Lines.Append('Haldle: ' + IntToStr(ThreadDownload.Handle));
  7.     TThreadList
  8.   end;




(Por cierto, eso de ThreadID y Handle lo puse para ver que era lo que hacía, por curioso, pensé que de alguna forma podía dirigirme a un thread con esos datos, pero no sé hacerlo en el caso de que sería así)

Bueno, hasta aquí todo correcto, lanza los 10 threads al instante y todos hacen su tarea..
Peeeero aquí el problema, que qué pasa si ahora quiero cancelar/pausar/reanudar todos los threads?
Cómo me dirigiría a ellos ?
Si antes lo hacía ThreadDownload.suspended .resume .terminate
Ahora cómo lo hago ?

Como podrán ver mi thread se crea con 2 parámetros, uno donde envío un memo para que pueda escribir el thread un log, para ver lo que pasa y que está descargando.. y el otro número, en este caso el número desde el que empezará a bajar el fichero, suponiendo que los fichero están en el servidor con el nombre de fichero0.rar fichero1.rar, etc (Esto para hacer más fácil entender el ejemplo)

Bueno, esa es mi pregunta, quiero poder tener el control de caa uno de los threads, saber cuando terminó cada uno, etc

Ya he visto muchos ejemplos, incluidos los de neftali que ahí crea igual que yo los threads, en un for.

Otra cosa.. si cada thread que crearía tendría un nombre diferente si se podría dirigirse a el, como este ejemplo:




delphi
  1.   for i := 0 to 9 do
  2.   begin
  3.     Memo1.Lines.Append('Lanzamos thread # ' + IntToStr(i));
  4.     ThreadDownload + inttostr(1) := TThreadDownload.Create(Memo1, 10);
  5.  
  6.     TThreadList
  7.   end;




ya se que esto ThreadDownload + inttostr(1) está mal, pero es un ejemplo nada mas de que si podría hacer esto podría dirigirme a cada threads por su nombre, pero no creo que esto sea lo correcto.

Espero que me hayan entendido y me haya explicado de forma clara.

Me gustaría si es que me dan la solución me pongan un pequeño ejemplo de como implementarlo, que es más intuitivo.

Desde ya muchas gracias a todo el que quiera colaborar con mi problema!

PD : He visto que se podría usar Tlist o TThreadList, si esto es así, ¿cómo podría usarlo? Si no sirve esto, olviden esta línea xD
  • 0

#2 BlackDaemon

BlackDaemon

    Member

  • Miembros
  • PipPip
  • 22 mensajes

Escrito 02 octubre 2009 - 12:49

Bueno, he visto que la solución es usar esto como bien decía, las Tlist y TThreadLIst, he logrado meter los objetos thread creados en la Tlist.add(MyThread) igual pararlos todos, o pausarlos, recorriendo con Count
pero tengo un problema.

Si tengo 10 objetos en un Tlist, estos se añaden al crear los 10 threads, entonces si estos 10 threads están en funcionamiento puedo terminarlos todos, y al terminar recorro el Tlist y a cada uno le hago un Terminate y un Tlist.Delete(i) hasta aquí todo bien, los termina y los va quitando de la lista
Peeero si los hilos terminan por su propia cuenta, estos terminaron con un TTMyThread.destroy; en la clase TMyThread, pero en la Tlist de mi form Principal siguen estando ahí los objetos.

Ahora lo que yo quiero hacer es... mientras vayan terminando los threads con un Destroy, se vaya quitando de la lista Tlist el objeto, así si el usuario termina los threads o si ellos terminan por su cuenta, la Tlist estará vacía.
Esto para controlar la Tlist y ir creando más Threads de acuerdo a la cantidad que tiene, unos salen, otros entran.. Thread pool xD

He buscado en internet y encontré algunos ejemplos en la web de embarcadero, pero están usando la API, y no la clase thread, en si, solo quisiera saber como eliminar un thread de mi Tlist si este termina solo.

Gracias!
  • 0

#3 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 02 octubre 2009 - 04:44

Efectivamente llegaste a la conclusión de crear una lista... A mi me gusta mas el bajo nivel y probablemente hubiese guardado en un array los punteros a los threads o su Pids. En definitiva esa es la solución.

Ahora lo que precisas es que un Thread que termine lo comunique a la aplicación o que ésta se lo pueda preguntar al Thread. Pues o bien provocas que el hilo que termine modifique una variable o llame a una función de tu Form, o bien activas una bandera para indicar que terminó en cuanto se consulte.

Saludos.
  • 0

#4 BlackDaemon

BlackDaemon

    Member

  • Miembros
  • PipPip
  • 22 mensajes

Escrito 02 octubre 2009 - 05:21

Hola

Bueno, a ver, estoy liadísimo con esto.. pegaré aquí mi código que he estado probando.. tanto el main como la clase thread.

Con esto creo los threads:



delphi
  1. procedure TForm1.btnCrearClick(Sender: TObject);
  2. var
  3.   ThreadDownload : TThreadDownload;
  4.   i : Integer;
  5.   ThreadList : TList;
  6. begin
  7.   ThreadList := ThreadSessionList.LockList;
  8.  
  9.   for i := 0 to 2 do
  10.   begin
  11.     //Memo1.Lines.Append('Lanzamos thread ' + IntToStr(i));
  12.     ThreadDownload := TThreadDownload.Create(Memo1, 10);
  13.     ThreadDownload.memo2 := Memo2;
  14.     ThreadDownload.Resume;
  15.     memo2.Lines.Append('Lanzamos Thread: ' + IntToStr(ThreadDownload.ThreadID));
  16.     ThreadList.Add(ThreadDownload);
  17.   end;
  18.     memo2.Lines.Append('Tenemos Thread: ' + IntToStr(ThreadList.Count - 1));
  19.   ThreadSessionList.UnlockList;
  20.  
  21. end;




Con este paro todos...




delphi
  1. procedure TForm1.btnPararClick(Sender: TObject);
  2. var
  3.   i: Integer;
  4.   Thread : TThreadDownload;
  5.   ThreadList : TList;
  6. begin
  7.   ThreadList := ThreadSessionList.LockList;
  8.  
  9.   for i := ThreadList.Count - 1 downto 0 do
  10.   begin
  11.     Thread := TThreadDownload(ThreadList[i]);
  12.     Memo2.Lines.Append('Terminando Thread: ' + IntToStr(Thread.ThreadID));
  13.     Thread.Terminate; // terminate thread
  14.     ThreadSessionList.Remove(Thread); // debería hacer esto..
  15.     //ThreadList.Delete(i); // O hacer esto ?
  16.     //Thread.WaitFor; // Esto me tira error de controlador no válido  // make sure thread terminates
  17.   end;
  18.   ThreadSessionList.UnlockList;
  19.   // Es necesario esto si no hago un .remove o delete ?
  20.   //ThreadSessionList.Clear;    esto es necesario y ya hice .remove ?
  21.   //ThreadList.Clear // Esto es necesario si hago un .delete(i) ?
  22.   Memo2.Lines.Append('Terminados todos los threads');
  23.  
  24. end;




con este "intento" mirar cuales están activos (corriendo) y aquí es lo que quiero hacer funcionar la prueba, para saber que threads terminaron y cuales siguen activos.. esto para crear nuevos threads y llenar de nuevo la Tlist con ellos hasta una cierta cantidad, todo esto lo hago para por ej. crear 10 threads, esperar a que termine uno y luego crear otro, si terminan 5, pues creo 5, así hasta que termine la tarea , osea el for que crea los threads. Supuestamente pensé que si tengo activado que el thread se autodestruya con FreeOnterminate el valor del puntero (objeto) que guardo en el Tlist sea nil y así saber si ese Thread ya ha terminao y si es nil entonces hacer un Tlist.delete o ThreadList.remove no se cual la verdad, en el código pongo unos comentarios donde tengo la duda.



delphi
  1. procedure TForm1.btnPararClick(Sender: TObject);
  2. var
  3.   i: Integer;
  4.   Thread : TThreadDownload;
  5.   ThreadList : TList;
  6. begin
  7.   ThreadList := ThreadSessionList.LockList;
  8.  
  9.   for i := ThreadList.Count - 1 downto 0 do
  10.   begin
  11.     Thread := TThreadDownload(ThreadList[i]);
  12.     Memo2.Lines.Append('Terminando Thread: ' + IntToStr(Thread.ThreadID));
  13.     Thread.Terminate; // terminate thread
  14.     ThreadSessionList.Remove(Thread); // debería hacer esto..
  15.     //ThreadList.Delete(i); // O hacer esto ?
  16.     //Thread.WaitFor; // Esto me tira error de controlador no válido  // make sure thread terminates
  17.   end;
  18.   ThreadSessionList.UnlockList;
  19.   // Es necesario esto si no hago un .remove o delete ?
  20.   //ThreadSessionList.Clear;    esto es necesario y ya hice .remove ?
  21.   //ThreadList.Clear // Esto es necesario si hago un .delete(i) ?
  22.   Memo2.Lines.Append('Terminados todos los threads');
  23.  
  24. end;




Eso es todo, ahora en mi clase thread lo tengo así:



delphi
  1. type
  2.   TThreadDownload = class(TThread)
  3.   private
  4.     { Private declarations }
  5.     memo1 : TMemo;
  6.     nro : Integer;
  7.     idHttp : TIdHTTP;
  8.   protected
  9.     procedure Execute; override;
  10.     function PosEx(const SubStr, S: string; Offset: Cardinal = 1): Integer;
  11.     function CopyEntre(Cadena:string; Desde,Hasta:string):string;
  12.     function GetNroPag(html: TStringList): String;
  13.     function getUrlMembers(html: TStringList): TStringList;
  14.   public
  15.     memo2 : TMemo;
  16.     constructor Create(TheMemo: TMemo; TheNro: Integer);
  17.     destructor Destroy(); override;
  18.   end;
  19.  
  20. var
  21.   ThreadListDestroy : TList;
  22.   threads : Integer = 0;
  23. implementation
  24.  
  25. ....




había pensado pasarle la Tlist al thread y este en este evento quitarlo de esta manera:



delphi
  1. destructor TThreadDownload.Destroy;
  2. begin
  3.   //THrearList.remove(self) // Esto es lo que pensé  que funcionará pero es una tontería, no he sabido pasarle la lista al thread  y tampoco se si esto se puede o si es correcto. xD
  4.   Dec(threads);
  5.   memo2.Lines.Append('Thread: ' + IntToStr(self.ThreadID) + ' destruido');
  6.  
  7.   inherited;
  8. end;



Esa variable threads global puedo acceder desde el main, pero solo era para probar cuántos threads están activos, pero esto solo es una prueba ya que si podría eliminar el objeto de mi lista entonces para saber cuántos están activos solo me haría falta un .Count y listo :D pero no puedo aplicarlo por que no se quitarlo de la Tlist :(

en el execute del thread tengo esto:



delphi
  1. procedure TThreadDownload.Execute;
  2. var
  3.   i : Integer;
  4. begin
  5.   { Place thread code here }
  6.  
  7.   Inc(threads);
  8.   for i := 0  to 100 do
  9.   begin
  10.     if Self.Terminated then
  11.       Break;
  12.     memo1.Lines.Append(IntToStr(i));
  13.     Sleep(50);
  14.   end;



Esto para probar.

Ahora había pensando crear la ThreadList en el Thread.. pero el problema que pensé fué que por cada thread que cree, se crearán varias ThreadList o Tlist y no solo una, donde pueda ir añadiendo en el evento .create y quitarlos en el Destroy, y desde el main acceder a la lista y hacer ya lo necesario, pausar, terminar, etc


Espero me sepan ayudar y muchas gracias escafandra, como podrás ver creo que en teoría sé lo que tengo que hacer, pero otra cosa es la práctica.. llevo desde ayer aprendiendo threads y no se cómo llevar la teoría al código por mis escasos conocimientos de delphi, sepan disculpar.

Me gustaría mirar un poco de código, ya que he mirado los httpServer de indy, los serverTCP de indy y creo que usan algo así, ThreadList para contolar esto, pero no logré entender esto que quiero, saber si un hilo está activo o no y quitarlo de lalista, por favor código ;)

Desde ya muchas grácias y espero no ser muy dedundante en mi mensaje que estoy espeso y liádo :(

Adiós
  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 03 octubre 2009 - 12:42

Tienes distintas formas de detectar que un thread ha terminado. Voy a explicarte las formas mas sencillas, como ya te expuse.

1.- La mas sencilla, utilización de una bandera:
Antes de salir del método Execute, colocas una variable bool miembro de la clase a true, definida en la clase de tu thread. Esto indicará a cualquier consulta externa, que el hilo terminó.




delphi
  1. procedure TThreadDownload.Execute;
  2. var
  3.   i : Integer;
  4. begin
  5.   { Place thread code here }
  6.  
  7.   Inc(threads);
  8.   for i := 0  to 100 do
  9.   begin
  10.     if Self.Terminated then
  11.       Break;
  12.     memo1.Lines.Append(IntToStr(i));
  13.     Sleep(50);
  14.   end;
  15.   Terminado:= true;
  16. end;




2.- Provocación de un evento en el Form que crea los hilos. Algo mas compleja y exige mas especificidad entre Form y Thread, pero muy eficaz. La especificidad se puede eliminar complicando un poco mas las cosas, pero no creo que sea el momento. Pasos a seguir:


a- Creas un método en tu Form1 que se lame EvEndThread, por ejemplo. Aquí pondrás el código oportuno que tu formulario usará tras conocer que el Thread terminó. Ese método puede recibir un parámetro con el ThreadID que termina, así lo tienes localizado.

b- Creas un método en tu clase Thread que se llame EndNotify, por ejemplo. Este método lo que debe hacer es llamar al Form1.EvEndThread(ThreadID); Provocando que se ejecute el código pertinente. Ten en cuenta que tu Thread puede no haber terminadode ejecutarse, por lo tanto si lo sacas de tu lista, lo perderás. Asegúrate que FreeOnTerminate es true.

c- Antes de salir del método Execute, como en el caso anterior en el que usábamos la bandera, colocas esto:

delphi
  1. Synchronize(@EndNotify);


Que será el encargado real de hacer saltar tu evento EvEndThread en tu formulario.

3.- Uso de mensajes personalizados de Windows tipo WM_USER.

4.- .......

Espero que lo tengas un poquito mas claro. ;)

Saludos.






  • 0

#6 BlackDaemon

BlackDaemon

    Member

  • Miembros
  • PipPip
  • 22 mensajes

Escrito 03 octubre 2009 - 01:43

Hola

Bueno, creo que la primera forma no me serviría para lo que esoty tratando de hacer, ya que estoy manejando varios hilos, no solo uno, a ver.. digamos que utilizo esa forma vale, y creo 10 threads la bandera estará en True y cuando termine uno, en false, osea que todos modificarán la misma bandera, por esta razón pienso que no sirve, esto solo creo que serviría para UN solo thread.

La otra solución creo que no entendí muy bien :s

Pero yo anoche lo he solucionado de una forma algo chapuza pero es que estoy aprendiendo y al parecer funciona.

Lo que hice fué manejar threadList y Tlist o igul podía hacerlo sin las Tlist pero la solución que usé fué:

crear la threadlist, cuando lanzo los hilos los añado ahí, y como ya tenía controlada la forma de parar todos de un saque, recorriendo la lista, pero no podía ir quitándolo de la lista mientras terminaban solos si estaba freeOnTerminate en True, osea que cada hilo que creo le paso como parámetro el ThreadList, y desde el evento Destroy del thread hago un .delete(self) y me lo quita de la lista que manejo en el main.

La duda que tengo aquí (estoy aprendiendo punteros de paso xD) es que he leído que un Tlist acepta punteros, vale, al crear un thread de la forma objThread := TMyThread.create(par1, par2, TThreadList);
y haciendo un Threadlist.add(objThread) estoy guardando el puntero a la primera celda de la Lista??
Si hago un:

for i := 0 10 then
  obj := Thread:create(par1, par2, Threadlist);
  ThreadList.add(obj);

Esto es correcto ? osea, cuando lo recibe el objeto thread a la lista la trata como una sola, no ? o va creando copias de ella, esto último no creo por que desde el main puedo acceder a la list y cuando hago en delete desde thread lo elimina de la lista correctamente, pero sigo con la duda de cómo manejaría mi lista desde main y desd thread usando SOLO UNA lista ?
por que pienso que al enviarle como parámetro mi list este me está creando más y más, pero al parecer no, no sé xD

Saludos
  • 0

#7 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 03 octubre 2009 - 02:27

...creo que la primera forma no me serviría para lo que esoty tratando de hacer, ya que estoy manejando varios hilos, no solo uno, a ver.. digamos que utilizo esa forma vale, y creo 10 threads la bandera estará en True y cuando termine uno, en false, osea que todos modificarán la misma bandera....


No. Para eso está la programación orientada a objetos. La bandera debe ser miembro de la clase de tus Threads, por lo tanto se modifica sólo en el que termina.

...que cada hilo que creo le paso como parámetro el ThreadList, y desde el evento Destroy del thread hago un .delete(self) y me lo quita de la lista que manejo en el main.


Es una forma mas, sólo que debes de tener la precaución de utilizar el ThreadList desde tu Thread con la función Synchronize, si no, puedes tener resutados inesperados.


La duda que tengo aquí (estoy aprendiendo punteros de paso xD) es que he leído que un Tlist acepta punteros, vale, al crear un thread de la forma objThread := TMyThread.create(par1, par2, TThreadList);
y haciendo un Threadlist.add(objThread) estoy guardando el puntero...


Exacto, guardas un puntero al Thread creado, cada vez uno nuevo.

Saludos.
  • 0

#8 BlackDaemon

BlackDaemon

    Member

  • Miembros
  • PipPip
  • 22 mensajes

Escrito 04 octubre 2009 - 07:50

Hola

muchas gracias de nuevo escafandra por tus respuestas, como bien dices hay varias posibilidades y yo creo que hice una y espero que no de ningún problema.

Para el que necesite algo así la solución está en mis anteriores post, el código fuente de casi todo.

adiós
  • 0




IP.Board spam blocked by CleanTalk.