Este es mi próximo proyecto de prueba para ver qué biblioteca de subprocesos para Delphi me conviene mejor para mi tarea de "escaneo de archivos" que me gustaría procesar en varios subprocesos / en un grupo de subprocesos.
Para repetir mi objetivo: transformar mi "escaneo de archivos" secuencial de más de 500-2000 archivos del enfoque sin hilos a uno roscado. No debería tener 500 subprocesos ejecutándose al mismo tiempo, por lo tanto, me gustaría usar un grupo de subprocesos. Un grupo de subprocesos es una clase similar a una cola que alimenta varios subprocesos en ejecución con la siguiente tarea de la cola.
El primer intento (muy básico) se realizó simplemente extendiendo la clase TThread e implementando el método Execute (mi analizador de cadenas enhebrado).
Dado que Delphi no tiene una clase de grupo de subprocesos implementada de fábrica, en mi segundo intento intenté usar OmniThreadLibrary de Primoz Gabrijelcic.
OTL es fantástico, tiene muchísimas maneras de ejecutar una tarea en segundo plano, un camino a seguir si desea tener un enfoque de "disparar y olvidar" para entregar la ejecución roscada de partes de su código.
AsyncCalls por Andreas Hausladen
Nota: lo que sigue sería más fácil de seguir si primero descarga el código fuente.
Mientras exploraba más formas de ejecutar algunas de mis funciones de manera enhebrada, decidí probar también la unidad "AsyncCalls.pas" desarrollada por Andreas Hausladen. Andy AsyncCalls: llamadas a funciones asincrónicas unit es otra biblioteca que un desarrollador de Delphi puede usar para aliviar la molestia de implementar un enfoque roscado para ejecutar algún código.
Del blog de Andy: Con AsyncCalls puede ejecutar múltiples funciones al mismo tiempo y sincronizarlas en cada punto de la función o método que las inició... La unidad AsyncCalls ofrece una variedad de prototipos de funciones para llamar a funciones asincrónicas... ¡Implementa un grupo de subprocesos! La instalación es súper fácil: solo use llamadas asíncronas desde cualquiera de sus unidades y tendrá acceso instantáneo a cosas como "ejecutar en un hilo separado, sincronizar la interfaz de usuario principal, esperar hasta que termine".
Además de las AsyncCalls de uso gratuito (licencia MPL), Andy también publica con frecuencia sus propias soluciones para el IDE de Delphi como "Delphi Speed Up"y"Extensiones DDev"Estoy seguro de que has oído hablar (si no lo estás usando).
Llamadas asíncronas en acción
En esencia, todas las funciones AsyncCall devuelven una interfaz IAsyncCall que permite sincronizar las funciones. IAsnycCall expone los siguientes métodos:
//v 2.98 de asynccalls.pas
IAsyncCall = interfaz
// espera hasta que finalice la función y devuelve el valor de retorno
sincronización de funciones: entero;
// devuelve True cuando finaliza la función asincrónica
función terminada: booleana;
// devuelve el valor de retorno de la función asincrónica, cuando Finished es TRUE
función ReturnValue: Integer;
// le dice a AsyncCalls que la función asignada no debe ejecutarse en la amenaza actual
procedimiento ForceDifferentThread;
final;
Aquí hay una llamada de ejemplo a un método que espera dos parámetros enteros (devolver una IAsyncCall):
TAsyncCalls. Invocar (Método Asíncrono, i, Aleatorio (500));
función TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): integer;
empezar
resultado: = sleepTime;
Sueño (sleepTime);
TAsyncCalls. VCLInvoke (
procedimiento
empezar
Registro (Formato ('hecho> nr:% d / tareas:% d / dormido:% d', [tasknr, asyncHelper. TaskCount, sleepTime]));
final);
final;
Las llamadas TAsync. VCLInvoke es una forma de sincronizar con su hilo principal (hilo principal de la aplicación - su interfaz de usuario de la aplicación). VCLInvoke regresa de inmediato. El método anónimo se ejecutará en el hilo principal. También hay VCLSync que regresa cuando se llamó al método anónimo en el hilo principal.
Grupo de subprocesos en AsyncCalls
Volver a mi tarea de "escaneo de archivos": al alimentar (en un bucle for) el grupo de subprocesos asynccalls con una serie de TAsyncCalls. Invocar llamadas (), las tareas se agregarán al grupo interno y se ejecutarán "cuando llegue el momento" (cuando las llamadas agregadas previamente hayan finalizado).
Espere a que todas las llamadas IAsync terminen
La función AsyncMultiSync definida en asnyccalls espera a que finalicen las llamadas asíncronas (y otros identificadores). Hay unos pocos sobrecargado formas de llamar a AsyncMultiSync, y aquí está la más simple:
función AsyncMultiSync (const Lista: gama de IAsyncCall; WaitAll: Boolean = True; Milisegundos: Cardenal = INFINITO): Cardenal;
Si quiero tener "esperar todo" implementado, necesito completar una matriz de IAsyncCall y hacer AsyncMultiSync en porciones de 61.
My AsnycCalls Helper
Aquí hay una parte de TAsyncCallsHelper:
ADVERTENCIA: código parcial! (código completo disponible para descargar)
usos AsyncCalls;
tipo
TIAsyncCallArray = gama de IAsyncCall;
TIAsyncCallArrays = gama de TIAsyncCallArray;
TAsyncCallsHelper = clase
privado
fTasks: TIAsyncCallArrays;
propiedad Tareas: TIAsyncCallArrays leer fTareas
público
procedimiento Agregar tarea(const llamada: IAsyncCall);
procedimiento Esperar todos;
final;
ADVERTENCIA: código parcial!
procedimiento TAsyncCallsHelper. Esperar todos;
var
i: entero;
empezar
para i: = alta (tareas) Abajo a Bajo (tareas) hacer
empezar
AsyncCalls. AsyncMultiSync (Tareas [i]);
final;
final;
De esta forma, puedo "esperar todo" en fragmentos de 61 (MAXIMUM_ASYNC_WAIT_OBJECTS), es decir, esperar matrices de IAsyncCall.
Con lo anterior, mi código principal para alimentar el grupo de subprocesos se ve así:
procedimiento TAsyncCallsForm.btnAddTasksClick (Remitente: TObject);
const
nrItems = 200;
var
i: entero;
empezar
asyncHelper. MaxThreads: = 2 * Sistema. CPUCount;
ClearLog ('inicio');
para i: = 1 a nrItems hacer
empezar
asyncHelper. AddTask (TAsyncCalls. Invocar (Método asíncrono, i, Aleatorio (500)));
final;
Log ('todo en');
// espera todo
//asyncHelper.WaitAll;
// o permitir cancelar todo no iniciado haciendo clic en el botón "Cancelar todo":
mientras no asyncHelper. Todo terminado hacer Solicitud. ProcessMessages;
Registro ('terminado');
final;
¿Cancelalo todo? - Tiene que cambiar las llamadas asíncronas.pas :(
También me gustaría tener una forma de "cancelar" aquellas tareas que están en el grupo pero que están esperando su ejecución.
Desafortunadamente, AsyncCalls.pas no proporciona una forma simple de cancelar una tarea una vez que se ha agregado al grupo de subprocesos. No hay IAsyncCall. Cancelar o IAsyncCall. DontDoIfNotAlreadyExecuting o IAsyncCall. No me importa.
Para que esto funcione, tuve que cambiar AsyncCalls.pas tratando de alterarlo lo menos posible, así que que cuando Andy lanza una nueva versión solo tengo que agregar algunas líneas para tener mi idea de "Cancelar tarea" trabajando.
Esto es lo que hice: agregué un "procedimiento Cancelar" a IAsyncCall. El procedimiento Cancelar establece el campo "FCancelled" (agregado) que se verifica cuando el grupo está a punto de comenzar a ejecutar la tarea. Necesitaba alterar ligeramente la IAsyncCall. Finalizado (para que los informes de una llamada finalicen incluso cuando se cancela) y TAsyncCall. Procedimiento InternExecuteAsyncCall (no ejecutar la llamada si se ha cancelado).
Puedes usar WinMerge para localizar fácilmente las diferencias entre el asynccall.pas original de Andy y mi versión alterada (incluida en la descarga).
Puede descargar el código fuente completo y explorar.
Confesión
¡DARSE CUENTA! :)
los Cancelar invocación El método evita que se invoque AsyncCall. Si AsyncCall ya está procesada, una llamada a CancelInvocation no tiene efecto y la función Cancelada devolverá False ya que AsyncCall no se canceló.
los Cancelado El método devuelve True si la cancelación de invocación de AsyncCall fue cancelada.
los Olvidar El método desvincula la interfaz IAsyncCall de la AsyncCall interna. Esto significa que si la última referencia a la interfaz IAsyncCall desaparece, la llamada asincrónica se seguirá ejecutando. Los métodos de la interfaz arrojarán una excepción si se llama después de llamar a Forget. La función asincrónica no debe llamar al hilo principal porque podría ejecutarse después del TThread. El mecanismo de sincronización / cola fue cerrado por el RTL, lo que puede causar un bloqueo muerto.
Sin embargo, tenga en cuenta que aún puede beneficiarse de mi AsyncCallsHelper si necesita esperar a que todas las llamadas asíncronas terminen con "asyncHelper". WaitAll "; o si necesita "Cancelar todo".