Embarcadero Delphi
Запуск потока при сохранении графического интерфейса пользователя
Поиск…
Отзывчивый графический интерфейс с использованием потоков для фоновой работы и PostMessage для отчета из потоков
Сохранение графического интерфейса при длительном процессе требует либо очень сложных «обратных вызовов», чтобы позволить графическому интерфейсу обрабатывать свою очередь сообщений, либо использовать потоки (фоновые) (рабочие).
Уклонение от любого количества потоков для выполнения некоторых работ обычно не является проблемой. Веселье начинается, когда вы хотите, чтобы графический интерфейс показывал промежуточные и конечные результаты или сообщал о прогрессе.
Отображение чего-либо в графическом интерфейсе требует взаимодействия с элементами управления и / или очереди сообщений / насоса. Это всегда должно быть сделано в контексте основного потока. Никогда в контексте какой-либо другой темы.
Есть много способов справиться с этим.
В этом примере показано, как вы можете это сделать, используя простые потоки, позволяя графическому интерфейсу получить доступ к экземпляру потока после его завершения, установив FreeOnTerminate
в значение false
и сообщив, когда поток «сделан» с помощью PostMessage
.
Примечания к условиям гонки: ссылки на рабочие потоки хранятся в массиве в форме. Когда поток завершен, соответствующая ссылка в массиве становится нулевой.
Это потенциальный источник условий гонки. Как и использование «Running» boolean, чтобы было легче определить, есть ли еще какие-то потоки, которые нужно закончить.
Вам нужно будет решить, нужно ли вам защищать этот ресурс с помощью блокировок или нет.
В этом примере, в его нынешнем виде, нет необходимости. Они изменяются только в двух местах: в StartThreads
метода и HandleThreadResults
метода. Оба метода выполняются только в контексте основного потока. Пока вы держите его таким образом и не начинаете называть эти методы из контекста разных потоков, у них нет возможности для создания условий гонки.
Нить
type
TWorker = class(TThread)
private
FFactor: Double;
FResult: Double;
FReportTo: THandle;
protected
procedure Execute; override;
public
constructor Create(const aFactor: Double; const aReportTo: THandle);
property Factor: Double read FFactor;
property Result: Double read FResult;
end;
Конструктор просто устанавливает частные члены и устанавливает FreeOnTerminate в False. Это необходимо, так как это позволит основному потоку запрашивать экземпляр потока для его результата.
Метод execute выполняет свой расчет, а затем отправляет сообщение в дескриптор, который он получил в своем конструкторе, чтобы сказать, что он сделан:
procedure TWorker.Execute;
const
Max = 100000000;var
i : Integer;
begin
inherited;
FResult := FFactor;
for i := 1 to Max do
FResult := Sqrt(FResult);
PostMessage(FReportTo, UM_WORKERDONE, Self.Handle, 0);
end;
В этом примере важно использовать PostMessage
. PostMessage
«just» помещает сообщение в очередь сообщения сообщения основного потока и не ждет его обработки. Он асинхронный по своей природе. Если вы будете использовать SendMessage
вы будете кодировать себя в рассол. SendMessage
помещает сообщение в очередь и ждет, пока оно не будет обработано. Короче говоря, это синхронно.
Объявления для пользовательского сообщения UM_WORKERDONE объявляются как:
const
UM_WORKERDONE = WM_APP + 1;
type
TUMWorkerDone = packed record
Msg: Cardinal;
ThreadHandle: Integer;
unused: Integer;
Result: LRESULT;
end;
UM_WORKERDONE
const использует WM_APP
в качестве отправной точки для своего значения, чтобы гарантировать, что он не мешает никаким значениям, используемым Windows или Delphi VCL (как рекомендовано MicroSoft).
форма
Любая форма может использоваться для запуска потоков. Все, что вам нужно сделать, это добавить к нему следующих членов:
private
FRunning: Boolean;
FThreads: array of record
Instance: TThread;
Handle: THandle;
end;
procedure StartThreads(const aNumber: Integer);
procedure HandleThreadResult(var Message: TUMWorkerDone); message UM_WORKERDONE;
О, и примерный код предполагает существование Memo1: TMemo;
в объявлениях формы, которые он использует для «ведения журнала и отчетности».
FRunning
можно использовать для предотвращения щелчка GUI во время работы. FThreads
используется для хранения указателя экземпляра и дескриптора созданных потоков.
Процедура запуска потоков имеет довольно простую реализацию. Он начинается с проверки того, есть ли уже набор ожидающих потоков. Если это так, он просто выходит. Если нет, он устанавливает флаг в значение true и запускает потоки, предоставляющие каждому свой собственный дескриптор, чтобы они знали, куда отправлять сообщение «сделано».
procedure TForm1.StartThreads(const aNumber: Integer);
var
i: Integer;
begin
if FRunning then
Exit;
FRunning := True;
Memo1.Lines.Add(Format('Starting %d worker threads', [aNumber]));
SetLength(FThreads, aNumber);
for i := 0 to aNumber - 1 do
begin
FThreads[i].Instance := TWorker.Create(pi * (i+1), Self.Handle);
FThreads[i].Handle := FThreads[i].Instance.Handle;
end;
end;
Рукоятка потока также помещается в массив, потому что это то, что мы получаем в сообщениях, сообщающих нам, что поток выполнен и наличие его вне экземпляра потока облегчает доступ. Наличие дескриптора, доступного вне экземпляра потока, также позволяет нам использовать FreeOnTerminate
для True
если нам не нужен экземпляр для получения его результатов (например, если они были сохранены в базе данных). В этом случае, конечно, нет необходимости хранить ссылку на экземпляр.
Веселье в реализации HandleThreadResult:
procedure TForm1.HandleThreadResult(var Message: TUMWorkerDone);
var
i: Integer;
ThreadIdx: Integer;
Thread: TWorker;
Done: Boolean;
begin
// Find thread in array
ThreadIdx := -1;
for i := Low(FThreads) to High(FThreads) do
if FThreads[i].Handle = Cardinal(Message.ThreadHandle) then
begin
ThreadIdx := i;
Break;
end;
// Report results and free the thread, nilling its pointer and handle
// so we can detect when all threads are done.
if ThreadIdx > -1 then
begin
Thread := TWorker(FThreads[i].Instance);
Memo1.Lines.Add(Format('Thread %d returned %f', [ThreadIdx, Thread.Result]));
FreeAndNil(FThreads[i].Instance);
FThreads[i].Handle := nil;
end;
// See whether all threads have finished.
Done := True;
for i := Low(FThreads) to High(FThreads) do
if Assigned(FThreads[i].Instance) then
begin
Done := False;
Break;
end;
if Done then
begin
Memo1.Lines.Add('Work done');
FRunning := False;
end;
end;
Этот метод сначала ищет поток, используя дескриптор, полученный в сообщении. Если совпадение найдено, оно извлекает и сообщает результат потока с помощью экземпляра ( FreeOnTerminate
был False
, помните?), А затем заканчивается: освобождение экземпляра и установка ссылки на экземпляр и дескриптор на нуль, указав, что этот поток не является более актуальным.
Наконец, он проверяет, работает ли какой-либо из потоков. Если ни один не найден, сообщается «все сделано», а флаг FRunning
установлен на False
чтобы можно было запустить новую партию работы.