Я согласен с Робом.Не используйте событие, используйте виртуальный метод.Но даже если бы вы использовали событие и использовали его «назначенность», чтобы сигнализировать, есть ли работа, которую нужно сделать, вам нужно будет защитить элемент FOnExecute, так как он может быть установлен из разных потоков.
В одномиз наших классов потоков мы используем команды для выполнения чего-то похожего:
procedure TCommandThread.SetCommand(const Value: ICommand);
begin
Lock;
try
Assert(not IsAvailable, 'Command should only be set AFTER the thread has been claimed for processing');
FCommand := Value;
if Assigned(FCommand) then
MyEvent.SetEvent;
finally
Unlock;
end;
end;
Поскольку SetCommand (установщик Команды) может быть вызван из любого старого потока, установка члена FCommand защищена критической секцией потока, которая являетсязаблокирован и разблокирован с помощью методов Lock и Unlock.
Сигнализация MyEvent выполнена, потому что наш класс потока использует член TEvent для ожидания работы.
procedure TCommandThread.Execute;
begin
LogDebug1.SendFmtMsg('%s.Execute : Started', [ClassName]);
// keep running until we're terminated
while not Terminated do
try
// wait until we're terminated or cleared for take-off by the threadpool
if WaitForNewCommand then
if Assigned(FCommand)
and not Terminated then
// process the command if we're told to do so
CommandExecute;
except
LogGeneral.SendFmtError('%s.Execute : Exception occurred :', [ClassName]);
LogGeneral.SendException;
end;
LogDebug1.SendFmtMsg('%s.Execute : Finished', [ClassName]);
end;
WaitForNewCommand возвращается, когда MyEvent сигнализируется.Это делается, когда команда назначена, но также и когда (текущая) команда отменяется, когда поток завершается и т. Д. Обратите внимание, что Termination проверяется снова перед вызовом CommandExecute.Это сделано потому, что, когда WaitForNewCommand возвращается, мы можем оказаться в ситуации, когда была назначена и команда, и завершение вызова.В конце концов, сигнализация события может быть выполнена дважды из разных потоков, и мы не знаем, когда и в каком порядке что-либо произошло.
CommandExecute - это виртуальный метод, который могут переопределять разные классы потоков.В реализации по умолчанию он обеспечивает всю обработку статуса, связанную с выполнением команд, поэтому сами команды могут сконцентрироваться на своих собственных вещах.
procedure TCommandThread.CommandExecute;
var
ExceptionMessage: string;
begin
Assert(Assigned(FCommand), 'A nil command was passed to a command handler thread.');
Assert(Status = chsIdle, 'Attempted to execute non-idle command handler thread');
// check if the thread is ready for processing
if IsAvailable then // if the thread is available, there is nothing to do...
Exit;
try
FStatus := chsInitializing;
InitializeCommand;
FStatus := chsProcessing;
try
ExceptionMessage := '';
CallCommandExecute;
except
on E: Exception do begin
ExceptionMessage := E.Message;
LogGeneral.SendFmtError('%s.CommandExecute: Exception occurred during commandhandler thread execution:', [ClassName]);
LogGeneral.SendException;
end;
end;
finally
FStatus := chsFinalizing;
FinalizeCommand;
FStatus := chsIdle;
FCommand := nil;
// Notify threadpool we're done, so it can terminate this thread if necessary :
DoThreadFinished;
// Counterpart to ClaimThreadForProcessing which is checked in IsAvailable.
ReleaseThreadForProcessing;
end;
end;
CallCommandExecute - это место, где через несколько уровней косвенности вызывается метод Execute FCommand игде настоящая работа команды сделана.Вот почему этот вызов напрямую защищен блоком try-exc.Кроме того, каждая команда сама по себе отвечает за безопасность потока в отношении ресурсов, которые она использует.
ClaimThreadForProcessing и ReleaseThreadForProcessing используются для запроса и освобождения потока.Ради скорости они не используют блокировку потока, а используют механизм блокировки для изменения значения члена класса FIsAvailable, который объявлен как указатель и используется как логическое значение:
TCommandThread = class(TThread)
// ...
FIsAvailable: Pointer;
function TCommandThread.ClaimThreadForProcessing: Boolean;
begin
Result := Boolean(CompatibleInterlockedCompareExchange(FIsAvailable, Pointer(False), Pointer(True)));
// (See InterlockedExchange help.)
end;
function TCommandThread.ReleaseThreadForProcessing: Boolean;
begin
FIsAvailable := Pointer(True);
Result := IsAvailable;
end;
Если естьобработка "finally" в методе CommandExecute должна выполняться независимо от исключений, вызванных другими вызовами в этом процессе, вам придется использовать вложенные try-finally, чтобы убедиться в этом.Вышеупомянутый метод был упрощен от нашего реального кода, и фактический блок finally - это набор вложенных попыток finally, чтобы гарантировать, что DoThreadFinished и т. Д. Вызывается независимо от исключений в FinalizeCommand (и других промежуточных вызовах).