Поток Delphi, который ожидает данных, обрабатывает их, а затем возобновляет ожидание

Мне нужно создать поток в Delphi со следующими характеристиками:

  • ожидает, пока основной поток не добавит данные в общую очередь.
  • обрабатывает все данные в очереди, возвращая результаты в основной поток (для этой последней части я просто отправлю сообщения в Главное окно). Обработка занимает много времени, поэтому новые данные могут быть добавлены в очередь, пока рабочий поток обрабатывает предыдущие записи.
  • возобновляет ожидание, используя как маленький процессор циклы, насколько это возможно.

Я не могу отправлять сообщения потоку, так как у него нет дескриптора окна.

должен ли я использовать какой-то вариант WaitForObject? Если да, то чего ждать? Если нет, то как я могу заставить поток ждать, а затем пробудить его, когда новые данные поступают в очередь?

я читал Многопоточность-Путь Delphi, что, похоже, не отвечает на мой вопрос. Возможно!--18-->OmniThreadLibrary могу делать то, что мне нужно; я не могу сказать, так как документов мало. Я недостаточно знаю о потоках в целом, чтобы понять, поможет ли библиотека здесь и как ее использовать (или даже почему использовать ее вместо работы с потомками TThread).

4 ответов


OmniThreadLibrary определенно может помочь вам здесь. Тест 5 из дистрибутива OTL должен помочь вам начать.

в этой демонстрации кнопка "Пуск" создает поток и устанавливает некоторые параметры и таймер (которые вы можете удалить в своем коде, если это не нужно). "Изменить сообщение" отправляет сообщение в поток, и это сообщение обрабатывается в методе omchangemessage потока. Затем поток отправляет некоторую информацию обратно клиенту (OMSendMessage в этой демонстрации, но вы можете сделать это в том же сообщение, в котором вы будете выполнять свою работу), и основной поток получает это сообщение через компонент OmniEventMonitor. Кнопка "стоп" останавливает рабочий поток.

Если больше сообщений поступает, пока поток занят, они будут поставлены в очередь и обработаны, как только ваш рабочий метод завершит свою работу. Когда делать нечего, поток будет ждать следующего сообщения, используя нулевые циклы процессора в процессе.

редактировать

в Delphi 2009 и выше,фон Рабочий pattern обеспечивает более простое решение.


WaitForSingleObject () может ждать нескольких типов объектов синхронизации. Вы можете использовать объект синхронизации Windows "event" (который не имеет ничего общего с событием Delphi). Вы создаете событие (в SyncObjs, IIRC есть оболочка Delphi TEvent) и вызываете WaitForSingleObject, чтобы дождаться сигнала этого события. Когда вам нужно пробудить поток, вы вызываете SetEvent, чтобы поместить событие в сигнальное состояние и WaitForSingleObject возвращает. Вы можете заставить поток ждать одного (или все) нескольких объектов с помощью WaitForMultipleObjects() - это также скажет вам, какой объект стал сигналом.


вы можете определенно отправлять сообщения в поток, даже если у него нет дескриптора окна. Просто используйте PostThreadMessage() вместо SendMessage() или PostMessage(). Здесь будет больше информации о StackOverflow, если вы ищете PostThreadMessage() в теге [delphi] - я не думаю, что это хорошая идея дублировать все здесь.

но если вы не разбираетесь в программировании потоков, то начинать с OTL вместо низкоуровневого материала действительно может быть хорошо.


вот простой пример как это можно сделать...

const
  WM_MY_RESULT = WM_USER + ;

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;