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 después de terminar la función que lo creó.
- Preferiblemente la estructura será una variable global o perteneciente 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.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