用于指示线程完成的方法比MsgWaitForMultipleObjects更好?

时间:2016-09-27 23:30:07

标签: multithreading delphi

所以我遇到了类似的代码:

aThread := TmyThread.Create(param1, param2);
h := aThread.handle;
repeat
  if (MsgWaitForMultipleObjects(1, h, False, INFINITE, QS_ALLINPUT) = WAIT_OBJECT_0)
    then break;
  Application.ProcessMessages
until False;

aThread.Free;
myProgressBar.Progress := myProgressBar.Max;  //an internal component, not really important here

我认为,这是为了提供一种更新GUI的方法,因此它不会被阻止,但也允许更新结束进程GUI(进度条),而一些长期任务是发生。

但它包含可怕的Application.ProcessMessages。

我已阅读The Darkside of Application.ProcessMessages以及许多其他Delphi博客,建议在使用Application.ProcessMessages时,现在是时候使用新线程了。

那么,逐步淘汰这种保持主/ GUI线程闲置的方法是明智的,例如显示的{匿名线程方法here?或其他什么?

这个Noob很困惑为什么它建议调用Application.ProcessMessages的进程是Thread的一个很好的候选者,但是有问题的线程依赖于我们不被告知的事情。要做!

1 个答案:

答案 0 :(得分:1)

主要的想法是不要等待线程。线程完成后应通知您的表单。换句话说,应该在完成线程之后执行的代码应该被隔离到一个单独的过程(参见TForm1.ThreadCompletedHandler),线程应该在完成后调用它。

这是一个小样本:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;

type
  TmyThread = class(TThread)
  private
    FParam1, FParam2: Integer;
    FUpdateProc: TProc<TmyThread, Integer>;
    FCompleteProc: TProc<TmyThread>;

    procedure SyncCompleteProc;
    procedure QueueUpdateProc(APosition: Integer);
  protected
    procedure Execute; override;
  public
    constructor Create(AParam1, AParam2: Integer;
      AUpdateProc: TProc<TmyThread, Integer>;
      ACompleteProc: TProc<TmyThread>);
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    myProgressBar: TProgressBar;
    procedure Button1Click(Sender: TObject);
  private
    FThread: TThread;

    procedure ThreadUpdateHandler(AThread: TMyThread; APosition: Integer);
    procedure ThreadCompletedHandler(AThread: TMyThread);
  protected
    procedure UpdateActions; override;
  public
    destructor Destroy; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TmyThread }

constructor TmyThread.Create(AParam1, AParam2: Integer; AUpdateProc: TProc<TMyThread, Integer>;
  ACompleteProc: TProc<TMyThread>);
begin
  FParam1 := AParam1;
  FParam2 := AParam2;
  FUpdateProc := AUpdateProc;
  FCompleteProc := ACompleteProc;
  inherited Create(False);
end;

procedure TmyThread.Execute;
var
  I: Integer;
begin
  //inherited; - abstract
  try
    I := FParam1;
    while not Terminated and (I < FParam2) do
    begin
      Sleep(1000);
      Inc(I);
      QueueUpdateProc(I);
    end;
  finally
    if Assigned(FCompleteProc) then
      TThread.Queue(Self, SyncCompleteProc);
  end;
end;

procedure TmyThread.QueueUpdateProc(APosition: Integer);
begin
  if Terminated or not Assigned(FUpdateProc) then
    Exit;

  TThread.Queue(Self,
    procedure
    begin
      FUpdateProc(Self, APosition);
    end);
end;

procedure TmyThread.SyncCompleteProc;
begin
  FCompleteProc(Self);
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
const
  param1 = 1;
  param2 = 5;
begin
  myProgressBar.Min := param1;
  myProgressBar.Max := param2 + 1;
  myProgressBar.Position := param1;

  FThread := TmyThread.Create(param1, param2, ThreadUpdateHandler, ThreadCompletedHandler);
end;

destructor TForm1.Destroy;
begin
  if Assigned(FThread) then
    FThread.Terminate;

  inherited;
end;

procedure TForm1.ThreadCompletedHandler(AThread: TmyThread);
begin
  try
    if not AThread.Terminated then // check form is not destroye yet
    begin
      FThread := nil;
      myProgressBar.Position := myProgressBar.Max;  //an internal component, not really important}
    end;
  finally
    FreeAndNil(AThread);
  end;
end;

procedure TForm1.ThreadUpdateHandler(AThread: TMyThread; APosition: Integer);
begin
  if not AThread.Terminated then // check form is not destroye yet
    myProgressBar.Position := APosition;
end;

procedure TForm1.UpdateActions;
begin
  inherited;
  Button1.Enabled := not Assigned(FThread);
end;

end.

和DFM文件

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 290
  ClientWidth = 554
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 24
    Top = 16
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object myProgressBar: TProgressBar
    Left = 120
    Top = 108
    Width = 150
    Height = 17
    TabOrder = 1
  end
end

NB 在此示例表单中不等待线程,因此我们可能会在线程终止之前关闭应用程序。这个样本应该不是问题,因为它很简单,但在现实中你可能需要等待Form.Destroy中的线程或创建一些线程管理器,它应该在应用程序完成之前等待所有正在运行的线程。