Поток 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;