9

我需要在 Delphi 中创建一个具有以下特征的线程:

  • 等到主线程将数据添加到共享队列。
  • 处理队列中的所有数据,将结果返回到主线程(对于最后一部分,我将只向主窗口发送消息)。处理是耗时的,因此在工作线程正在处理以前的条目时,可能会将新数据添加到队列中。
  • 恢复等待,使用尽可能少的 cpu 周期。

我无法向线程发送消息,因为它没有窗口句柄。

我应该使用 WaitForObject 的一些变体吗?如果是这样,那还要等什么呢?如果没有,那么我怎样才能让线程等待,然后在新数据进入队列时唤醒它?

我读过Multithreading - The Delphi Way,这似乎没有回答我的问题。也许 OmniThreadLibrary可以做我需要的;我不知道,因为文档很少。一般来说,我对线程的了解还不够,无法弄清楚该库是否会在这里提供帮助以及如何使用它(甚至为什么要使用它而不是仅仅使用 TThread 后代)。

4

4 回答 4

13

OmniThreadLibrary 绝对可以在这里为您提供帮助。OTL 发行版中的测试 5 应该可以帮助您入门。

在此演示中,“开始”按钮创建线程并设置一些参数和计时器(如果不需要,您可以在代码中删除)。“更改消息”向线程发送消息,该消息在线程的 OMChangeMes​​sage 方法中处理。线程然后将一些信息发送回客户端(本演示中的 OMSendMessage,但您可以在您将在其中进行工作的同一消息中执行此操作)并且主线程通过 OmniEventMonitor 组件接收此消息。“停止”按钮停止工作线程。

如果在您的线程忙碌时有更多消息到达,它们将在您的工作方法完成其工作后立即排队和处理。当无事可做时,线程将在进程中使用零 CPU 周期等待下一条消息。

编辑

在 Delphi 2009 及更高版本中,Background Worker模式提供了一个更简单的解决方案。

于 2009-11-14T21:09:54.323 回答
2

WaitForSingleObject() 可以等待几种类型的同步对象。您可以使用 Windows“事件”同步对象(与 Delphi 事件无关)。您创建事件(在 SyncObjs、IIRC 中有一个 Delphi TEvent 包装器),然后调用 WaitForSingleObject 以等待该事件发出信号。当您必须唤醒线程时,您调用 SetEvent 将事件置于信号状态,然后 WaitForSingleObject 返回。您可以使用 WaitForMultipleObjects() 让线程等待多个对象中的一个(或全部) - 它还会告诉您哪个对象已发出信号。

于 2009-11-14T22:02:57.750 回答
1

您绝对可以将消息发送到线程,即使它没有窗口句柄。只需使用PostThreadMessage()代替SendMessage()or PostMessage()。如果您在 [delphi] 标签中搜索,这里会有更多关于 StackOverflow的信息PostThreadMessage()- 我认为在这里复制所有内容不是一个好主意。

但是如果你不了解线程编程,那么从 OTL 开始而不是低级的东西可能确实是一件好事。

于 2009-11-14T21:23:45.920 回答
1

这是一个简单的例子,你可以如何做到这一点......

const
  WM_MY_RESULT = WM_USER + $1;

type
  TMyThread = class(TThread)
  private
    FKilled: Boolean;
    FListLock: TRTLCriticalSection;
    FList: TList;
    FJobAdded: TEvent;
  protected
    procedure Execute; override;
    procedure DoJob(AJob: Integer);
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure Kill;
    procedure PushJob(AJob: Integer);
    function  JobCount: Integer;
    function  GetJob: Integer;
  end;


  TThreadingForm = class(TForm)
    lstResults: TListBox;
    se: TSpinEdit;
    btn: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnClick(Sender: TObject);
  private
    FThread: TMyThread;
    procedure OnMyResultMessage(var Msg: TMessage); message WM_MY_RESULT;
  public
    { Public declarations }
  end;

var
  ThreadingForm: TThreadingForm;

implementation

{$R *.dfm}

{ TMyThread }

constructor TMyThread.Create(CreateSuspended: Boolean);
begin
  FKilled := False;
  InitializeCriticalSection(FListLock);
  FList := TList.Create;
  FJobAdded := TEvent.Create(nil, True, False, 'job.added');
  inherited;
end;

destructor TMyThread.Destroy;
begin
  FList.Free;
  FJobAdded.Free;
  DeleteCriticalSection(FListLock);
  inherited;
end;

procedure TMyThread.DoJob(AJob: Integer);
var
  res: Integer;
begin
  res := AJob * AJob * AJob * AJob * AJob * AJob;
  Sleep(1000); // so it would take some time
  PostMessage(ThreadingForm.Handle, WM_MY_RESULT, res, 0);
end;

procedure TMyThread.Execute;
begin
  inherited;
  while not FKilled or not Self.Terminated do
  begin
    EnterCriticalSection(FListLock);
    if JobCount > 0 then
    begin
      LeaveCriticalSection(FListLock);
      DoJob(GetJob)
    end
    else
    begin
      FJobAdded.ResetEvent;
      LeaveCriticalSection(FListLock);
      FJobAdded.WaitFor(10000);
    end;
  end;
end;

function TMyThread.GetJob: Integer;
begin
  EnterCriticalSection(FListLock);
  try
    Result := Integer(FList[0]);
    FList.Delete(0);
  finally
    LeaveCriticalSection(FListLock);
  end;
end;

function TMyThread.JobCount: Integer;
begin
  EnterCriticalSection(FListLock);
  Result := FList.Count;
  LeaveCriticalSection(FListLock);
end;

procedure TMyThread.Kill;
begin
  FKilled := True;
  FJobAdded.SetEvent;
  Terminate;
end;

procedure TMyThread.PushJob(AJob: Integer);
begin
  EnterCriticalSection(FListLock);
  try
    FList.Add(Pointer(AJob));
    FJobAdded.SetEvent;
  finally
    LeaveCriticalSection(FListLock);
  end;
end;

{ TThreadingForm }

procedure TThreadingForm.OnMyResultMessage(var Msg: TMessage);
begin
  lstResults.Items.Add(IntToStr(Msg.WParam));
end;

procedure TThreadingForm.FormCreate(Sender: TObject);
begin
  FThread := TMyThread.Create(False);
end;

procedure TThreadingForm.FormDestroy(Sender: TObject);
begin
  FThread.Kill;
  FThread.WaitFor;
  FThread.Free;
end;

procedure TThreadingForm.btnClick(Sender: TObject);
begin
  FThread.PushJob(se.Value);
end;
于 2009-11-15T15:17:29.950 回答