Tras el hilo comenzado
aquí y la contestación que dí para el uso de threads, he pensado desarrollar un ejemplo a partir de la sugerencia que hice.
Creo que el tema de los Threads en muchas ocasiones no está claro y desde el punto de vista de la API de Windows quizás menos aún, así que voy a desarrollar el ejemplo a bajo nivel, desde la API esperando que se entienda y sirva de base para su uso en otras aplicaciones diferentes a la tratada.
El ejemplo lo voy a escribir usando
lazarus pero no habrá problema para trasladarlo a
delphi o a
C.
La utilización de Threads con la API de Windows se centra en la API
CreateThread. Esta API, entre otros, precisa de dos parámetros fundamentales y sencillos de entender:
1.-
El punto de entrada del hilo, que para nosotros va a representarse por una función del tipo:
function MyFunc(param: pointer): DWORD; stdcall;
2.-
El parámetro pasado a dicha función que será un puntero a una estructura con los datos pertinentes que usará la función del Thread.
Mas información la encontrareis
aquí.
El caso del
parámetro merece una especial atención:
- Será un puntero a una estructura que diseñaremos a nuestro gusto pero con todos los datos necesarios y sin usar punteros que apunten a elementos externos.
- La estructura (contenido de ese puntero) debe existir durante toda la vida del Thread. Cuando la estructura de crea dentro de una función, su vida es la misma que la de dicha función (se crea en la pila), sin embargo es posible que el thread dure bastante mas o que incluso comience despues de terminar la función que lo creó.
- Preferiblemente la estructura será una variable global o pereteneciente a una clase perdurable.
- En caso de precisar punteros, éstos apuntarán a datos dentro de la propia estructura.
El Thread termina cuando lo haga su función pero su handle ha de ser cerrado (API
CloseHandle).
Vistos estos detalles generales voy a desarrollar el ejemplo que consistirá en crear una función que mueva carpetas, con la API
ShFileOperation, mediante Threads. Cada llamada a dicha función creará un nuevo Thread hasta un número máximo que consideremos.
Para controlar los Threrads que están activos voy a aprovechar la estructura parámetro donde coloco una bandera (Runing) que será True mientras viva el Thread. De esta manera sabremos los hilos que terminan para poder ser reutilizados.
La
estructura parámetro queda definida como:
TSHFParam = record
fos: TSHFileOpStruct; // Parámetro para la API ShFileOperation
StrFrom: String; // Nombre de la carpeta origen
StrTo: String; // Nombre de la carpeta destino
Runing: boolean; // Bandera
end;
PSHFParam = ^TSHFParam;
La estructura
TSHFileOpStruct contiene dos PCHAR a la lista de archivos o carpetas origen y destino. Esos punteros apuntarán a los elementos String StrFrom y StrTo de nuestra
TSHFParam asegurando que apuntarán a lugares válidos durante la vida del Thread. La presencia de fos en nuestra estructura es por comodidad en la escritura del código.
Lo siguiente es crear un array de elementos de nuestra estructura. Dicho array será una variable global o estática de nuestra función.
No es conveniente permitir demasiados Threads al tiempo por lo que limitarlos a 5 puede ser suficiente. En el caso de hacer mas llamadas a nuestra función para mover archivos, esta esperará a tener un Thread libre para usarlo, en caso contrario se creará el Thread correspondiente y seguirá sin espera alguna.
Localizamos un Thread libre recorriendo el array y mirando si el semáforo Runing es False.
Creo que la filosofía del planteamiento es fácil de entender y es trasladable a otro tipo de problemática.
Coloco el código completo y comentado del ejemplo:
uses
windows, shellapi;
// Estructura del parámetro a pasar a cada Thread
TSHFParam = record
fos: TSHFileOpStruct;
StrFrom: String;
StrTo: String;
Runing: boolean;
end;
PSHFParam = ^TSHFParam;
const MAX_THREAD = 5; // Máximo número de thread permitidos al mismo tiempo
var
// Array de parámetros para los MAX_THREAD posibles
AParam: array [0..MAX_THREAD-1] of TSHFParam;
function ThShFileOperation(Param: PSHFParam): DWORD; stdcall;
begin
// Ejecuto la función del Thread y al terminar lo m,arco como finalizado
Result:= ShFileOperation(Param^.fos);
Param^.Runing:= false;
end;
procedure MoveDir(const fromDir, toDir: string);
var
hThread: Cardinal;
i: integer;
begin
// Busco un thread no usado entre los MAX_THREAD permitidos
i:= 0; while AParam[i].Runing do i:= (i+1) mod MAX_THREAD;
// Preparo el parámetro para el nuevo thread
with AParam[i] do
begin
StrFrom:= fromDir + #0;
StrTo:= toDir + #0;
ZeroMemory(@fos, SizeOf(TSHFileOpStruct));
fos.wFunc := FO_MOVE;
fos.fFlags := FOF_FILESONLY or FOF_SILENT or FOF_NOCONFIRMATION;
fos.pFrom := PChar(StrFrom);
fos.pTo := PChar(StrTo);
end;
// Creo el nuevo thread y si tengo éxito lo marco como usado
hThread:= CreateThread(nil, 0, @ThShFileOperation, @AParam[i], 0, PDWORD(0)^);
AParam[i].Runing:= boolean(hThread);
// Cierro el Handle
CloseHandle(hThread);
end;
Y su uso:
procedure TForm1.Button1Click(Sender: TObject);
begin
MoveDir('D:\Copia (1) de Ejemplo', 'D:\Nueva1');
MoveDir('D:\Copia (2) de Ejemplo', 'D:\Nueva2');
MoveDir('D:\Copia (3) de Ejemplo', 'D:\Nueva3');
MoveDir('D:\Copia (4) de Ejemplo', 'D:\Nueva4');
end;
Espero que este escueto tutorial sirva de base para la solución de otras cuestiones que requieran el uso de Threads en Windows.
Saludos.
Edito para subir un proyecto lazarus ejemplo