Usando TEvent e MsgWaitForMultipleObjects no bloqueio do thread principal
Eu encontrei o código interessante desse Remy. Delphi: Como criar e usar Thread localmente?
Isso pode ser feito para que eu possa fazer vários threads e esperar até que todos sejam concluídos e, em seguida, continuar com o thread principal? Eu tentei assim mas sem sucesso ...
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;
Respostas
Isso pode ser feito para que eu possa fazer vários threads e esperar até que todos estejam concluídos
sim. Você simplesmente precisa criar vários TEventobjetos, um para cada um TThread, e armazenar todos os seus Handles em uma matriz para passar para 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;
Dito isso, você pode, como alternativa, se livrar dos TEventobjetos e aguardar os TThread.Handles. Um thread Handleé sinalizado para uma operação de espera quando o thread é totalmente encerrado. A única pegadinha é que TThread.CreateAnonymousThread()cria uma propriedade de TThreadquem FreeOnTerminateé True, então você terá que desligar isso 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 qualquer forma, observe que MsgWaitForMultipleObjects()está limitado a esperar um máximo de 63 ( MAXIMUM_WAIT_OBJECTS[64] - 1) identificadores por vez. A WaitForMultipleObjects()documentação explica como contornar essa limitação, se você precisar:
Para esperar mais de MAXIMUM_WAIT_OBJECTS identificadores, use um dos seguintes métodos:
- Crie um encadeamento para aguardar nos identificadores MAXIMUM_WAIT_OBJECTS e, em seguida, espere nesse thread mais os outros identificadores. Use esta técnica para quebrar as alças em grupos de MAXIMUM_WAIT_OBJECTS.
- Chame RegisterWaitForSingleObject para aguardar em cada identificador. Um thread de espera do pool de threads espera em objetos registrados MAXIMUM_WAIT_OBJECTS e atribui um thread de trabalho depois que o objeto é sinalizado ou o intervalo de tempo limite expira.
Ou você pode simplesmente processar sua lista em lotes menores, digamos, no máximo 50-60 itens por vez.