PDA

Ver la Versión Completa : Threads en unit


Ramsay
12-04-2016, 20:48:35
Hola , estoy haciendo una unit que hace multithreading , el codigo de la unit :


unit probando;

{$POINTERMATH ON}

interface

uses
System.SysUtils, Vcl.Dialogs, Windows, TlHelp32, ShellApi,
Classes, Math, Vcl.Forms, Vcl.Imaging.jpeg, Vcl.StdCtrls, Vcl.Graphics,
Vcl.Controls,
ComObj, ActiveX, Variants, Messages;

type
T_probando = class
private

public
constructor Create;
destructor Destroy; override;
function loader_tester(argument: string; count: integer): string;
end;

implementation

constructor T_probando.Create;
begin
inherited Create;
//
end;

destructor T_probando.Destroy;
begin
inherited Destroy;
end;

// Function loader_tester

type
thread_loader_tester = class(TThread)
protected
procedure Execute; override;
public
argument: string;
end;

procedure thread_loader_tester.Execute;
var
command: string;
info: string;
begin
TThread.Synchronize(nil,
procedure
begin
command := 'c:/xampp/titulo.txt';
info := '';
ShellExecute(0, 'open', Pchar(command), Pchar(info), nil, SW_NORMAL);
end);
end;

function T_probando.loader_tester(argument: string; count: integer): string;
var
thread_now: thread_loader_tester;
i: integer;
fThreadRefCount: integer;
check: integer;
begin
try
fThreadRefCount := 0;
begin

For i := 1 to count do
begin
thread_now := thread_loader_tester.Create(True);
thread_now.argument := argument;
thread_now.FreeOnTerminate := True;
thread_now.Start;
Inc(fThreadRefCount);
end;

While fThreadRefCount > 1 do
// While (fThreadRefCount > 1) and (fThreadRefCount < count) do
begin
Application.ProcessMessages;
CheckSynchronize;
end;

Result := 'ok';
end;
Result := 'ok';
except
begin
Result := 'error';
end;
end;
end;

end.


Debe funcionar tanto en aplicaciones graficas como en consola , lamentablemente estoy mejorandola para que funcione en consolas , el codigo de la consola.


program test;

{$APPTYPE CONSOLE}
{$R *.res}

uses
Windows, System.SysUtils, probando;

var
test_now: T_probando;

begin
try
test_now := T_probando.Create();
Writeln('[+] Response');
Writeln('response : '+test_now.loader_tester('c:/xampp/titulo.txt', 5));
Readln;
except
on E: Exception do
Writeln(E.Classname, ': ', E.Message);
end;

end.


Funciona , pero entra en un ciclo infinito :


While fThreadRefCount > 1 do


Intente checkeando que verificara la cantidad de threads y ahi terminara :


While (fThreadRefCount > 1) and (fThreadRefCount < count) do


Pero si hago esto no se ejecuta ningun thread.

:eek::eek::eek:

¿ Alguien podria ayudarme ?

jhonny
12-04-2016, 21:19:13
Pensaría que quizá necesitas hacer un decremento de fThreadRefCount en el ciclo:



While fThreadRefCount > 1 do
begin
Application.ProcessMessages;
CheckSynchronize;
Dec(fThreadRefCount); //Algo así quizá...
end;

AgustinOrtu
12-04-2016, 21:30:08
No entiendo nada de tu codigo

Por que usas un Syncronize para ejecutar un ShellExecute? Eso ya esta creando un proceso externo a tu aplicacion, no hace falta que sincronices nada

Por que el Application.ProcessMessages?
Por que el CheckSynchronize?

jhonny
12-04-2016, 21:32:45
No entiendo nada de tu codigo

Por que usas un Syncronize para ejecutar un ShellExecute? Eso ya esta creando un proceso externo a tu aplicacion, no hace falta que sincronices nada

Por que el Application.ProcessMessages?
Por que el CheckSynchronize?

Bueno, me hago las mismas preguntas, aunque asumí que son "para algo" y ya, por eso mi respuesta.

AgustinOrtu
12-04-2016, 21:33:12
Para aplicaciones de consola, si se debe usar CheckSynchronize (http://docwiki.embarcadero.com/Libraries/en/System.Classes.CheckSynchronize)

Lee la documentacion

Es necesario asignar el valor del metodo a WakeMainThread (http://docwiki.embarcadero.com/Libraries/en/System.Classes.WakeMainThread) El problema es que desde una aplicacion con interfaz grafica no deberias tocar esa variable, y usar TThread.Syncronize

Yo me replantearia el diseño

Ramsay
13-04-2016, 00:01:39
El shellexecute es solo un ejemplo , en esa parte del codigo va todo el funcionamiento o el codigo que agregare en el futuro.Estoy estudiando multithreads pero hay muy pocos ejemplos.

Ramsay
13-04-2016, 01:44:45
Perdon , hago otro post porque me olvide de un detalle importante , uso Synchronize (http://docwiki.embarcadero.com/Libraries/en/System.Classes.CheckSynchronize) porque estoy usandolo en una consola.

AgustinOrtu
13-04-2016, 02:28:35
ShellExecute es asincronico, no te sirve para aprender threads

Mejor pon un for de i hasta algun numero muy grande, por ejemplo algun int64

Ramsay
13-04-2016, 02:56:25
Ok , pero el tema principal como lo soluciono ? , intente como dijo jhonny pero aun asi vuelve a lo mismo , o se vuelve eterno o no se ejecuta ninguno.

AgustinOrtu
13-04-2016, 03:55:17
Ok esto es muy interesante, admito como siempre que multithreading es dificil y me cuesta dominarlo. Pero encima en consola es distinto porque se te cierra el programa :D

Esto es lo que invente, es bastante "versatil"

Clase que hereda de TThread:


unit Unit4;

interface

uses
Classes;

type
TThreadInutil = class(TThread)
private
FName: string;
FTope: Int64;
FUltimoValor: Int64;
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean; const AName: string; const ATope: Int64);
property Name: string read FName;
property Tope: Int64 read FTope;
property UltimoValor: Int64 read FUltimoValor;
end;

implementation

uses
SysUtils;

constructor TThreadInutil.Create(CreateSuspended: Boolean; const AName: string; const ATope: Int64);
begin
inherited Create(CreateSuspended);
FreeOnTerminate := True;
FName := AName;
FUltimoValor := 0;
if FTope > MaxLongint then
FTope := MaxLongint
else
FTope := ATope;
end;

procedure TThreadInutil.Execute;
var
I: Int64;
begin
I := 0;
while (I < Tope) and not Terminated do
begin
Inc(I);
FUltimoValor := I;
Synchronize(NIL, procedure begin WriteLn(Format('Name: %s I = %d', [Name, I])); end);
end;
end;

end.


Basicamente cuenta desde 0 hasta el numero que le decimos en el constructor

En el metodo Execute que es reimplementado, imprime el "nombre" y el numero actual en la salida estandar, usando Syncronize

El programa principal crea dos TThreadInutiles, los cuales inician apenas termina su correspondiente constructor.

Luego, hay una pequeña clase TInformador porque el evento OnTerminate es de tipo TNotifyEvent y para agregarle un manejador hay que tener un objeto (TNotifyEvent es un procedure of object, no podes poner un procedimiento suelto, a menos no de forma idiomatica)

El informador recibe la notificacion cuando cada hilo termina su trabajo, y si es el "t1", le agrego una lectura de entrada estandar pidiendo un numero: si es 0 le pedimos al t2 que termine, sino que continue


Hay que consultar el estado de los Thread, si estan trabajando (propiedad Booleana Finished) y si alguno esta haciendo algo, es necesario invocar a CheckSyncronize


program Project2;
{$APPTYPE CONSOLE}

uses
SysUtils,
Classes,
Unit4 in 'Unit4.pas';

type
TInformador = class
public
procedure ThreadTerminado(Sender: TObject);
end;

var
t1, t2: TThreadInutil;
Informador: TInformador;

procedure TInformador.ThreadTerminado(Sender: TObject);
var
Num: Integer;
begin
Writeln(Format('%s Termino: Ultimo Valor %d', [Sender.ClassName, TThreadInutil(Sender).UltimoValor]));

if Sender = t1 then
begin
Writeln('Ingrese 0 para detener t2, cualquier numero para seguir..');
Readln(Num);
if Num = 0 then
t2.Terminate;
end;
end;

begin
Informador := TInformador.Create;
t1 := TThreadInutil.Create(False, 'Thread 1', 1500);
t2 := TThreadInutil.Create(False, 'Thread 2', 1900);
try
t1.OnTerminate := Informador.ThreadTerminado;
t2.OnTerminate := Informador.ThreadTerminado;
while not(t1.Finished and t2.Finished) do
CheckSynchronize;
try
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
finally
Informador.Free;
Writeln;
Writeln('Terminado. Presione ENTER para salir');
Readln;
end;

end.

Ramsay
13-04-2016, 15:27:52
Te agradezco la ayuda AgustinOrtu , encontre una solucion poco elegante para mi codigo , uso una variable global que incremento en el TThread.Synchronize , entonces en el odioso bucle while :mad: , pongo una condicion donde si la variable global es igual al count hace un break y listo.Ahora me fijo como le puedo agregar un for a tu codigo para hacer multithreading.
gracias por la ayuda.

AgustinOrtu
13-04-2016, 18:09:03
Yo sigo pensando que esta mal planteado tu codigo

En el ejemplo cree dos threads, queres crear mas con un for, entonces create una estructura de threads (lista, pila, cola, arreglo, lo que quieras) y los vas metiendo ahi

No es correcto usar Application.ProcessMessages en una aplicacion de consola, eso se usa desde una aplicacion VCL

El codigo no es seguro y cuando falle, la razon va a estar enterrada muy dentro y muy dificil de ver

Una variable global es un problema cuando usas thread por que el acceso tiene que ser sincronizado, no solamente la vas a usar para lectura sino que tambien la queres modificar

DopeRider
14-04-2016, 15:59:31
Hola , estoy haciendo una unit que hace multithreading , el codigo de la unit : (...) ¿ Alguien podria ayudarme ?

¿Qué problema estás tratando de solucionar?