Поток Delphi, который ожидает данные, обрабатывает их, затем возобновляет ожидание - PullRequest
9 голосов
/ 14 ноября 2009

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

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

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

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

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

Ответы [ 4 ]

13 голосов
/ 15 ноября 2009

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

В этой демонстрации кнопка «Пуск» создает поток и устанавливает некоторые параметры и таймер (которые вы можете удалить в своем коде, если они не нужны). «Change message» посылает сообщение в поток, и это сообщение обрабатывается в методе OMChangeMessage потока. Затем поток отправляет некоторую информацию обратно клиенту (OMSendMessage в этой демонстрации, но вы можете сделать это в том же сообщении, в котором будете выполнять свою работу), и главный поток получает это сообщение через компонент OmniEventMonitor. Кнопка «Стоп» останавливает рабочий поток.

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

EDIT

В Delphi 2009 и выше шаблон Background Worker предоставляет более простое решение.

2 голосов
/ 15 ноября 2009

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

1 голос
/ 15 ноября 2009

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

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;
1 голос
/ 15 ноября 2009

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

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

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