В данной статье я разбирался с тем как работают события (TEvent) в потоках. Составил небольшой пример. Кроме главного, будут работать 2 потока. Один будет заполнять ProgressBar. Другой пытаться получить значение ProgressBar.Position и вывести его в заголовок формы. Естественно, пока работает первый поток – это у него не получится. Он будет ожидать результата до конца работы первого потока. Форма будет выглядеть следующим образом.
Описание кода
В uses добавим syncobjs
1 2 |
uses ...syncobjs |
Cоздадим классы 2 потоков
1 2 3 4 5 6 7 8 9 |
TFirstThread=class(TThread) protected procedure execute; override; end; TSecondThread=class(TThread) protected procedure execute; override; end; |
Создадим в глобальных переменных имена для 2 экземпляров потоков, а также переменную события
1 2 3 4 5 |
var Form: TForm; FirstThread:TFirstThread; SecondThread:TSecondThread; Event:Tevent; |
Создадим Event и уничтожим его в соответствующих событиях формы
1 2 3 4 5 6 7 8 9 |
procedure TForm1.FormCreate(Sender: TObject); begin Event := TEvent.Create( nil, //Security Attributes true, //Manual Reset false,// Initial State '' ); end; |
Уничтожение Event
1 2 3 4 |
procedure TForm1.FormDestroy(Sender: TObject); begin Event.Free; end; |
Теперь обработаем execute первого потока
1 2 3 4 5 6 7 8 9 10 11 |
procedure TFirstThread.execute; var i:integer; begin inherited; Event.ResetEvent; //Вошли в событие, поставили в несигнальное состояние for i :=Form1.ProgressBar.Min to Form1.ProgressBar1.Max do Form1.ProgressBar1.Position:=i; Sleep(50); Event.SetEvent; // Вышли из события end; |
Execute 2 потока
1 2 3 4 5 6 7 8 9 10 11 12 |
procedure TSecondThread.execute; begin inherited; if (Event.WaitFor(250) <> wrSignaled) and (FirstThread.Started) then Form.Caption := 'Первый поток ещё занят'; if Event.WaitFor(Infinite)=wrSignaled then Form.Caption := ('Первый поток освободился. ' +'Значение Progress Bar = '+inttostr(Form1.ProgressBar1.Position)); end; |
Вызов первого потока
1 2 3 4 5 6 |
procedure TForm1.StartClick(Sender: TObject); var i:integer; begin FirstThread:=TFirstthread.Create(false);// автозапуск с false FirstThread.FreeOnTerminate:=true; end; |
Вызов второго потока
1 2 3 4 5 |
procedure TForm1.GetProgressPositionClick(Sender: TObject); begin SecondThread:=TSecondthread.Create(false);// автозапуск с false SecondThread.FreeOnTerminate:=true; end; |
Результат
При нажатии на Start
При нажатии на GetProgressPosition во время выполнения потока 1
После завершения потока 1
Первое общее впечатление от работы с TEvent – чем-то напомнило работу критических секций с одной стороны – то есть на время выполнения первым потоком своего участка кода доступ к ProgressBar был закрыт.
С другой стороны мы использовали метод WaitFor, его перегруженную версию, который также есть у потоков. Но у потоков у нас только один вариант – дождаться конца исполнения. Так как согласно документации метод определен следующим образом
1 |
function WaitFor: LongWord; |
А у TEvent этот же метод определен вот так
1 |
function WaitFor(Timeout: LongWord): TWaitResult; overload; override; |
Поэтому мы могли использовать конструкцию, вроде
1 2 |
if (Event.WaitFor(250) <> wrSignaled) and (FirstThread.Started) then Form.Caption := 'Первый поток ещё занят'; |
То есть заглянули в событие на 250 милисек. и проверили – занят ли поток событием. То есть, если событие в сигнальном состоянии (wrSignaled), тогда участок кода между
1 2 3 4 5 |
Event.ResetEvent; //Вошли в событие, поставили в несигнальное состояние ...//some code here Event.SetEvent; // Вышли из события |
свободен для другого потока. А если в несигнальном(<>wrSignaled) – то тогда другой поток не может обратиться к этому участку кода.
Я бы сказал, что этот инструмент сочетает в себе возможности критических секций с одной стороны и некоторых методов потоков с другой (WaitFor). Очень, очень удобно для организации доступа к совместным ресурсам.