Utilisation de TEvent et MsgWaitForMultipleObjects pour bloquer le thread principal

Oct 08 2020

J'ai trouvé le code intéressant de Remy. Delphi: Comment créer et utiliser Thread localement?

Est-ce que cela peut être fait pour que je puisse faire plusieurs threads et attendre qu'ils soient tous terminés, puis continuer avec le thread principal? Je l'ai essayé comme ça mais sans succès ...

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;

Réponses

5 RemyLebeau Oct 08 2020 at 05:31

Est-ce que cela peut être fait pour que je puisse faire plusieurs threads et attendre qu'ils soient tous terminés

Oui. Vous devez simplement créer plusieurs TEventobjets, un pour chacun TThread, puis stocker tous leurs Handles dans un tableau pour les transmettre à 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;

Cela étant dit, vous pouvez également vous débarrasser des TEventobjets et attendre le TThread.Handles à la place. Un thread Handleest signalé pour une opération d'attente lorsque le thread est complètement terminé. Le seul problème est que TThread.CreateAnonymousThread()crée une propriété TThreaddont FreeOnTerminateest True, vous devrez donc la désactiver manuellement:

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;

Dans tous les cas, notez que cela MsgWaitForMultipleObjects()se limite à attendre un maximum de 63 ( MAXIMUM_WAIT_OBJECTS[64] - 1) poignées à la fois. La WaitForMultipleObjects()documentation explique comment contourner cette limitation, si vous devez:

Pour attendre plus de MAXIMUM_WAIT_OBJECTS handles, utilisez l'une des méthodes suivantes:

  • Créez un thread pour attendre sur les poignées MAXIMUM_WAIT_OBJECTS, puis attendez sur ce thread plus les autres poignées. Utilisez cette technique pour diviser les poignées en groupes de MAXIMUM_WAIT_OBJECTS.
  • Appelez RegisterWaitForSingleObject pour attendre chaque handle. Un thread d'attente du pool de threads attend les objets enregistrés MAXIMUM_WAIT_OBJECTS et affecte un thread de travail une fois que l'objet est signalé ou que l'intervalle de temporisation a expiré.

Ou, vous pouvez simplement traiter votre liste par lots plus petits, par exemple pas plus de 50 à 60 éléments à la fois.