Синхронизации потоков
Если вы создали шаблон класса автоматически, то, наверное, заметили комментарий, какой Delphi поместил в новый модуль. Он говорит: "Methods and properties of objects in visual components can only be used in a method called using Synchronize". Это значит, что обращение к визуальным компонентам возможно только путем вызова процедуры Synchronize. Давайте рассмотрим пример, но теперь наш поток не будет разогревать процессор зря, а будет делать что-либо полезное, например, прокручивать ProgressBar на форме. Как параметр в процедуру Synchronize передается метод нашего потока, но сам он передается без параметров. Параметры можно передать, прибавив поля нужного типа в описание нашего класса. У нас будет одно поле - тот же прогресс :
TNewThread = class (TThread)
private
Progress: integer;
procedure SetProgress;
protected
procedure Execute; override;
end;
...
procedure TNewThread.Execute;
var
i: integer;
begin
for i: = 0 to 100 do
begin
sleep (50);
Progress: = i;
Synchronize (SetProgress);
end;
end;
procedure TNewThread.SetProgress;
begin
Form1.ProgressBar1.Position: = Progress;
end;
Вот теперь ProgressBar двигается, и это полностью безопасно. А безопасно вот почему: процедура Synchronize на время приостанавливает выполнение нашего потока, и передает управление главного потока, то есть SetProgress выполняется в главном потоке. Это надо запомнить, потому что некоторые допускают ошибки, выполняя внутри Synchronize длительную работу, при этом, что очевидно, форма зависает на длительное время. Поэтому используйте Synchronize для выведения информации - тот же двигатель прогресса, обновления заглавий компонентов и тому подобное
Вы наверное заметили, что внутри цикла мы используем процедуру Sleep. В одинпотоковом дополнении Sleep используется редко, а вот в потоках его использовать очень удобно. Пример - бесконечный цикл, пока не выполнится какое-то условие. Если не вставить туда Sleep мы будем просто нагружать систему напрасной работой. Так работает Synchronize. Но есть еще один достаточно удобный способ передать информацию форме - посылка сообщения. Давайте рассмотрим и его. Для этого объявим константу:
const
PROGRESS_POS = WM_USER 1;
В объявление класса формы прибавим новый метод, а потом и его реализацию:
TForm1 = class (TForm)
Button1: TButton;
ProgressBar1: TProgressBar;
procedure Button1Click (Sender: TObject);
private
procedure SetProgressPos (var Msg : TMessage); message PROGRESS_POS;
public
(Public declarations)
end;
...
procedure TForm1.SetProgressPos (var Msg : TMessage);
begin
ProgressBar1.Position: = Msg.LParam;
end;
Теперь мы немного изменим, можно сказать даже упростим, реализацию метода Execute нашего потока :
procedure TNewThread.Execute;
var
i: integer;
begin
for i: = 0 to 100 do
begin
sleep (50);
SendMessage (Form1.Handle, PROGRESS_POS, 0, i);
end;
end;
Используя функцию SendMessage, мы посылаем окну программы сообщения, один из параметров которого содержит нужный нам прогресс. Сообщение становится в очередь, и в соответствии с этой очередью будет обработано главным потоком, где и выполнится метод SetProgressPos. Но здесь есть один нюанс: SendMessage, как и в случае с Synchronize, приостановит выполнение нашего потока, пока основной поток не обработает сообщения. Если использовать PostMessage этого не случится, наш поток отправит сообщение и продолжит свою работу, а уже когда оно там будет проработано - неважно. Какую из этих функций использовать - решать вам, все зависит от задания.
Вот, в принципе, мы и рассмотрели основные способы работы с компонентами VCL из потоков. А как быть, если в нашей программе не один новый поток, а несколько? И нужно организовать работу с одними и теми же данными? Здесь нам на помощь приходят другие способы синхронизации. Один из них мы и рассмотрим. Для его реализации нужно прибавить в проект модуль SyncObjs.
Критические секции
Работают они таким способом: внутри критической секции может работать только один поток, другие ожидают его завершения. Чтобы лучше понять, везде приводят сравнения с узкой трубой: представьте, с одной стороны "толпятся" потоки, но в трубу может "пролізти" только один, а когда он "пролізе" - начнет движение второй, и так по порядку. Еще проще понять это на примере и тем же ProgressBar 'ом. Следовательно, запустите один из примеров, приведенных раньше. Нажмите на кнопку, подождите несколько секунд, а потом нажмите еще раз. Что происходит? ProgressBar начал прыгать. Прыгает потому, что у нас работает не один поток, а два, и каждый из них передает разные значения прогрессу. Теперь немного переделаем код, в событии onCreate формы создадим критическую секцию:
var
Form1: TForm1;
CriticalSection: TCriticalSection;
...
procedure TForm1.FormCreate (Sender: TObject);
begin
CriticalSection: = TCriticalSection.Create;
end;
У TCriticalSection есть две нужны нам методу, Enter и Leave, соответственно вход и выход из нее. Поместим наш код в критическую секцию:
procedure TNewThread.Execute;
var
i: integer;
begin
CriticalSection.Enter;
for i: = 0 to 100 do
begin
sleep (50);
SendMessage (Form1.Handle, PROGRESS_POS, 0, i);
end;
CriticalSection.Leave;
end;
Попробуйте запустить программу и нажать несколько раз на кнопку, а потом посчитайте, сколько раз пройдет прогресс. Понятно, в чем суть? Первый раз, нажимая на кнопку, мы создаем поток, он занимает критическую секцию и начинает работу. Нажимаем второй - создается второй поток, но критическая секция занята, и он ожидает, пока ее не освободит первый. Третий, четвертый - все пройдут только по-черзі.
Критические секции удобно использовать при обработке одних и тех же данных (списков, массивов) разными потоками.
Дата добавления: 2016-02-27; просмотров: 434;