Usando TEvent y MsgWaitForMultipleObjects para bloquear el hilo principal
He encontrado este interesante código de Remy. Delphi: ¿Cómo crear y usar Thread localmente?
¿Se puede hacer esto para poder hacer varios subprocesos y esperar hasta que estén todos terminados y luego continuar con el subproceso principal? Lo intenté así pero sin éxito ...
procedure Requery(DataList: TStringList);
var
Event: TEvent;
H: THandle;
OpResult: array of Boolean;
i: Integer;
begin
Event := TEvent.Create;
try
SetLength(OpResult, DataList.Count);
for i:=0 to DataList.Count-1 do begin
TThread.CreateAnonymousThread(
procedure
begin
try
// run query in thread
OpResult[i]:=IsMyValueOK(DataList.Strings[i]);
finally
Event.SetEvent;
end;
end
).Start;
H := Event.Handle;
end;
while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) = (WAIT_OBJECT_0+1) do Application.ProcessMessages;
for i:=Low(OpResult) to High(OpResult) do begin
Memo1.Lines.Add('Value is: ' + BoolToStr(OpResult[i], True));
end;
finally
Event.Free;
end;
// Do next jobs with query
...
end;
Respuestas
¿Se puede hacer esto para poder hacer varios subprocesos y esperar hasta que estén todos terminados?
Si. Simplemente necesita crear varios TEventobjetos, uno para cada uno TThread, y luego almacenar todos sus Handles en una matriz para pasar a MsgWaitForMultipleObjects():
procedure Requery(DataList: TStringList);
var
Events: array of TEvent;
H: array of THandle;
OpResult: array of Boolean;
i: Integer;
Ret, Count: DWORD;
// moved into a helper function so that the anonymous procedure
// can capture the correct Index...
procedure StartThread(Index: integer);
begin
Events[Index] := TEvent.Create;
TThread.CreateAnonymousThread(
procedure
begin
try
// run query in thread
OpResult[Index] := IsMyValueOK(DataList.Strings[Index]);
finally
Events[Index].SetEvent;
end;
end
).Start;
H[Index] := Events[Index].Handle;
end;
begin
if DataList.Count > 0 then
begin
SetLength(Events, DataList.Count);
SetLength(H, DataList.Count);
SetLength(OpResult, DataList.Count);
try
for i := 0 to DataList.Count-1 do begin
StartThread(i);
end;
Count := Length(H);
repeat
Ret := MsgWaitForMultipleObjects(Count, H[0], False, INFINITE, QS_ALLINPUT);
if Ret = WAIT_FAILED then RaiseLastOSError;
if Ret = (WAIT_OBJECT_0+Count) then
begin
Application.ProcessMessages;
Continue;
end;
for i := Integer(Ret-WAIT_OBJECT_0)+1 to High(H) do begin
H[i-1] := H[i];
end;
Dec(Count);
until Count = 0;
for i := Low(OpResult) to High(OpResult) do begin
Memo1.Lines.Add('Value is: ' + BoolToStr(OpResult[i], True));
end;
finally
for i := Low(Events) to High(Events) do begin
Events[i].Free;
end;
end;
end;
// Do next jobs with query
...
end;
Dicho esto, alternativamente puede deshacerse de los TEventobjetos y esperar en TThread.Handlesu lugar la s. Un subproceso Handlese indica para una operación de espera cuando el subproceso se termina por completo. El único problema es que TThread.CreateAnonymousThread()crea una propiedad de TThreadquién FreeOnTerminatees True, por lo que tendrá que desactivarla manualmente:
procedure Requery(DataList: TStringList);
var
Threads: array of TThread;
H: array of THandle;
OpResult: array of Boolean;
i: Integer;
Ret, Count: DWORD;
// moved into a helper function so that the anonymous procedure
// can capture the correct Index...
procedure StartThread(Index: integer);
begin
Threads[Index] := TThread.CreateAnonymousThread(
procedure
begin
// run query in thread
OpResult[Index] := IsMyValueOK(DataList.Strings[Index]);
end
);
Threads[Index].FreeOnTerminate := False;
H[Index] := Threads[Index].Handle;
Threads[Index].Start;
end;
begin
try
SetLength(Threads, DataList.Count);
SetLength(H, DataList.Count);
SetLength(OpResult, DataList.Count);
for i := 0 to DataList.Count-1 do begin
StartThread(i);
end;
Count := Length(H);
repeat
Ret := MsgWaitForMultipleObjects(Count, H[0], False, INFINITE, QS_ALLINPUT);
if Ret = WAIT_FAILED then RaiseLastOSError;
if Ret = (WAIT_OBJECT_0+Count) then
begin
Application.ProcessMessages;
Continue;
end;
for i := Integer(Ret-WAIT_OBJECT_0)+1 to High(H) do begin
H[i-1] := H[i];
end;
Dec(Count);
until Count = 0;
for i := Low(OpResult) to High(OpResult) do begin
Memo1.Lines.Add('Value is: ' + BoolToStr(OpResult[i], True));
end;
finally
for i := Low(Threads) to High(Threads) do begin
Threads[i].Free;
end;
end;
// Do next jobs with query
...
end;
De cualquier manera, tenga en cuenta que MsgWaitForMultipleObjects()está limitado a esperar un máximo de 63 ( MAXIMUM_WAIT_OBJECTS[64] - 1) identificadores a la vez. La WaitForMultipleObjects()documentación explica cómo evitar esa limitación, si necesita:
Para esperar en más de MAXIMUM_WAIT_OBJECTS identificadores, utilice uno de los siguientes métodos:
- Cree un hilo para esperar en los identificadores MAXIMUM_WAIT_OBJECTS, luego espere en ese hilo más los otros identificadores. Utilice esta técnica para dividir los identificadores en grupos de MAXIMUM_WAIT_OBJECTS.
- Llame a RegisterWaitForSingleObject para esperar en cada identificador. Un subproceso de espera del grupo de subprocesos espera en MAXIMUM_WAIT_OBJECTS objetos registrados y asigna un subproceso de trabajo después de que se señala el objeto o expira el intervalo de tiempo de espera.
O simplemente puede procesar su lista en lotes más pequeños, digamos que no más de 50-60 artículos a la vez.