Windows 如何等待多个事件?

Windows 如何等待多个事件?,windows,delphi,winapi,delphi-7,Windows,Delphi,Winapi,Delphi 7,我正在使用的应用程序中几乎没有线程。 这些线程设置为FreeOnTerminate,不允许我更改此行为。 我看到老开发人员在主线程中等待信号的方式有些奇怪,如下所示: 让我们: 对于每个线程,我们都有一个事件,然后Length(Threads)=Length(FWaits) 这是在工作线程中编程的: destructor TBkgThread.Destroy; begin inherited; FEvent.SetEvent; end; 在这种情况下,我不知道调用inherited后是

我正在使用的应用程序中几乎没有线程。 这些线程设置为FreeOnTerminate,不允许我更改此行为。 我看到老开发人员在主线程中等待信号的方式有些奇怪,如下所示:

让我们:

对于每个线程,我们都有一个事件,然后
Length(Threads)=Length(FWaits)

这是在工作线程中编程的:

destructor TBkgThread.Destroy;
begin
  inherited;
  FEvent.SetEvent;
end;
在这种情况下,我不知道调用inherited后是否可以发送事件信号,但这不是问题所在

我知道WaitForMultipleObjects,所以我尝试将标有
[code]
的代码缩减为:

var Handles: array of THandle;

  SetLength(Handles, Length(FWaits));
  for I:= 0 to Length(FWaits)-1 do
    Handles[I]:= FWaits[I].Handle;

  case WaitForMultipleObjects(Length(Handles), @Handles[0], TRUE, INFINITE) of
    WAIT_FAILED: begin Label1.Caption:= 'WAIT_FAILED'; RaiseLastWin32Error; end;
    WAIT_OBJECT_0: Label1.Caption:= 'WAIT_OBJECT_0';
    WAIT_ABANDONED_0: Label1.Caption:= 'WAIT_ABANDONED_0';
    WAIT_TIMEOUT: Label1.Caption:= 'WAIT_TIMEOUT';
  end;
但它引发了一个windows错误:代码6

如何正确等待多个事件

[更新]

TBkgThread = class(TThread)
  private
    FEvent: TEvent;
  protected
    procedure Execute; override;
  public
    constructor Create(AEvent: TEvent);
    destructor Destroy; override;
  end;

  TForm1 = class(TForm)
    edThreads: TLabeledEdit;
    Button1: TButton;
    Button2: TButton;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    FThreads: array of TBkgThread;
    FWaits: array of TEvent;
  public
    { Public declarations }
  end;

{ TBkgThread }

constructor TBkgThread.Create(AEvent: TEvent);
begin
  inherited Create(False);
  FreeOnTerminate:= True;
  FEvent:= AEvent;
end;

destructor TBkgThread.Destroy;
begin
  inherited;
  FEvent.SetEvent;
end;

procedure TBkgThread.Execute;
var I,J,K: Integer;
begin
  while not Terminated do
  begin
    for I:= 0 to 10000 div 20 do
      for J:= 0 to 10000 div 20 do
        for K:= 0 to 10000 div 20 do;
    Sleep(1000);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I, C: Integer;
begin
  C:= StrToIntDef(edThreads.Text, 0);
  if C > 0 then
  begin
    SetLength(FThreads, C);
    SetLength(FWaits, C);
    for I:= 0 to C-1 do
    begin
      FWaits[I]:= TSimpleEvent.Create();
      FThreads[I]:= TBkgThread.Create(FWaits[I]);
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var I: Integer;
    Handles: array of THandle;
begin
  for I:= 0 to Length(FWaits)-1 do
    FThreads[I].Terminate;

  SetLength(Handles, Length(FWaits));
  for I:= 0 to Length(FWaits)-1 do
    Handles[I]:= FWaits[I].Handle;

  case WaitForMultipleObjects(Length(Handles), @Handles[0], TRUE, INFINITE) of
    WAIT_FAILED: begin RaiseLastWin32Error; Label1.Caption:= 'WAIT_FAILED'; end;
    WAIT_OBJECT_0: Label1.Caption:= 'WAIT_OBJECT_0';
    WAIT_ABANDONED_0: Label1.Caption:= 'WAIT_ABANDONED_0';
    WAIT_TIMEOUT: Label1.Caption:= 'WAIT_TIMEOUT';
  end;

//  for I:= 0 to Length(FWaits)-1 do
//    case FWaits[I].WaitFor(INFINITE) of
//      wrError: begin Label1.Caption:= 'WAIT_FAILED'; Break; end;
//      wrSignaled: Label1.Caption:= 'WAIT_OBJECT_0';
//      wrAbandoned: Label1.Caption:= 'WAIT_ABANDONED_0';
//      wrTimeout: Label1.Caption:= 'WAIT_TIMEOUT';
//    end;
end;

您传递的是第二个等待句柄的地址,而不是第一个。替换

@Handles[1]

其他一些建议:

  • 您必须立即调用
    RaiseLastError
    (或者实际上是
    RaiseLastError
    ),以便
    GetLastError
    可以检索所需API调用的错误代码。您在设置标签标题后调用它,这很可能导致意外调用
    SetLastError
  • 最好在重写的
    DoTerminate
    方法中设置事件。这样,您就不会从析构函数调用
    FEvent
    上的方法。如果构造函数引发,则
    FEvent
    可以是
    nil
  • 在终止线程时不应保留对空闲线程的引用。由于线程对象可以在任何时候被释放,因此您可以很容易地保存对已销毁对象的引用
  • 最后一点非常重要。当您在所有线程上循环调用
    Terminate
    时,显然没有遵守这一点。您需要决定是使用free-on-terminate,还是保留对线程的引用。你必须选择一个或另一个选项。你不能两全其美

    如果希望在终止线程时使用free,那么需要使用单独的事件来表示取消。将该事件传递给构造上的线程。测试设置的事件,而不是测试thread方法中是否终止了
    。设置要取消的事件

    然而,这一切都很奇怪。您说您希望在终止线程时使用free,但是您还希望等待所有线程完成。使用free-on-terminate线程会迫使您创建额外的事件来处理等待和取消。因为您无法保留对线程的引用。现在,您必须释放您创建的事件。因此,您避免了释放线程,而是将其替换为释放事件的要求

    您的方法比需要的复杂得多。我认为你应该:

    • 停止使用自由终止线程
    • 删除所有事件
    • 等待线程句柄
    • 等待完成后释放线程

    仅在不需要等待线程时才使用免费终止线程。

    我不知道是否需要SSCCE。如果需要,我可以发布。不,真的没有必要保留
    FThreads
    。你打算用它们做什么?你永远不能提及他们。事实上,你保留了这些参考资料,并告诉我你不能删除它们,这告诉我还有更多的事情你没有告诉我们。我不知道你为什么不做SSCCE。你不能调用FThreads[x]。在
    FreeOnTerminate
    线程上终止。如果你需要提前停止线程,你必须有一个同步对象来完成,比如TSimpleEvent。。。。或将FreeOnTerminate设置为false。问题发生在线程已经完成并在调用Terminate时释放时。我相信这是SSCCE中的一个输入错误,但无论如何我会检查。不,这就是问题所在。。。我不相信在我写答案的时候我甚至没有看到TOndrej的评论。但是,是的,这是对Ondrej的支持。关于最后一点,代码只使用这种方式来终止线程。你是说我应该只在线程自行终止时使用FreeOnTerminated?我在答案的末尾添加了更多的文本。基本上,我认为你需要停止使用免费终止。因为您正在等待线程。
    TBkgThread = class(TThread)
      private
        FEvent: TEvent;
      protected
        procedure Execute; override;
      public
        constructor Create(AEvent: TEvent);
        destructor Destroy; override;
      end;
    
      TForm1 = class(TForm)
        edThreads: TLabeledEdit;
        Button1: TButton;
        Button2: TButton;
        Label1: TLabel;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
      private
        FThreads: array of TBkgThread;
        FWaits: array of TEvent;
      public
        { Public declarations }
      end;
    
    { TBkgThread }
    
    constructor TBkgThread.Create(AEvent: TEvent);
    begin
      inherited Create(False);
      FreeOnTerminate:= True;
      FEvent:= AEvent;
    end;
    
    destructor TBkgThread.Destroy;
    begin
      inherited;
      FEvent.SetEvent;
    end;
    
    procedure TBkgThread.Execute;
    var I,J,K: Integer;
    begin
      while not Terminated do
      begin
        for I:= 0 to 10000 div 20 do
          for J:= 0 to 10000 div 20 do
            for K:= 0 to 10000 div 20 do;
        Sleep(1000);
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      I, C: Integer;
    begin
      C:= StrToIntDef(edThreads.Text, 0);
      if C > 0 then
      begin
        SetLength(FThreads, C);
        SetLength(FWaits, C);
        for I:= 0 to C-1 do
        begin
          FWaits[I]:= TSimpleEvent.Create();
          FThreads[I]:= TBkgThread.Create(FWaits[I]);
        end;
      end;
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    var I: Integer;
        Handles: array of THandle;
    begin
      for I:= 0 to Length(FWaits)-1 do
        FThreads[I].Terminate;
    
      SetLength(Handles, Length(FWaits));
      for I:= 0 to Length(FWaits)-1 do
        Handles[I]:= FWaits[I].Handle;
    
      case WaitForMultipleObjects(Length(Handles), @Handles[0], TRUE, INFINITE) of
        WAIT_FAILED: begin RaiseLastWin32Error; Label1.Caption:= 'WAIT_FAILED'; end;
        WAIT_OBJECT_0: Label1.Caption:= 'WAIT_OBJECT_0';
        WAIT_ABANDONED_0: Label1.Caption:= 'WAIT_ABANDONED_0';
        WAIT_TIMEOUT: Label1.Caption:= 'WAIT_TIMEOUT';
      end;
    
    //  for I:= 0 to Length(FWaits)-1 do
    //    case FWaits[I].WaitFor(INFINITE) of
    //      wrError: begin Label1.Caption:= 'WAIT_FAILED'; Break; end;
    //      wrSignaled: Label1.Caption:= 'WAIT_OBJECT_0';
    //      wrAbandoned: Label1.Caption:= 'WAIT_ABANDONED_0';
    //      wrTimeout: Label1.Caption:= 'WAIT_TIMEOUT';
    //    end;
    end;
    
    @Handles[1]
    
    @Handles[0]