Для организации параллельных процессов в Delphi предусмотрен особый тип данных Thread (поток). Название "поток" в данном случае не имеет ничего общего с файловым потоком (см. п. 13.5. на с. 111). Надо отметить, что работа с потоками достаточно сложна и неочевидна.
Создадим программу, выполняющую простейшее действие – сложение двух введенных чисел. Создаем новое приложение и сохраняем файл программы под именем unit1.pas (это важно!), а файл проекта – project1.dpr. Поместим на форму два компонента типа Tedit, кнопку и компонент Tlabel. В обработчике нажатия на кнопку напишем очевидный код:
procedure TForm1.Button1Click(Sender: TObject);
var a,b:real;
begin
TRY
a:=StrToFloat(Edit1.Text);
b:=StrToFloat(Edit2.Text)
EXCEPT
MessageDlg('Неверные данные', mtError,[mbOK],0);
EXIT
END;
Label2.Caption:=FloatToStr(a+b)
end;
А теперь заставим цвет формы плавно меняться, перебирая все возможные оттенки. Цвет формируется значениями интенсивности красного, синего и зеленого. Интенсивность каждого цвета хранится в одном байте и соответственно принимает значения от 0 до 255. Поэтому для перебора всех цветов можно использовать три вложенных цикла:
for red:=0 to 255 do
for green:=0 to 255 do
for blue:=0 to 255 do
begin
form1.Color:=RGB(Red,Green,Blue);
application.ProcessMessages
end;
Данный код должен постоянно выполняться во втором, дочернем, потоке, который будет создан нашим приложением.
Для создания потока выполним команду меню FileàNewàOtheràThread Object (
). В появившемся окошке следует ввести произвольное имя потока, к примеру, Time. Delphi автоматически создаст файл-заготовку для потока с описанием вида
type
Time = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
Сразу сохраним файл-заготовку под именем unit2.pas. Поток имеет процедуру Execute, внутри которой и вызывается код, исполняемый в потоке. Однако из процедуры Execute нельзя напрямую обращаться к компонентам. Это связано с тем, что в многопоточном приложении несколько потоков могут одновременно захотеть изменить одно и то же свойство одного и того же компонента. Скажем, при отрисовке, один и тот же пиксел один поток требует окрасить в черный цвет, а другой – в белый. Для исключения подобных коллизий необходимо применять так называемую синхронизацию потоков. Синхронизация обеспечивает последовательный доступ потоков к свойствам компонентов и позволяет избежать конфликтов.
Для синхронизации, прежде всего надо весь код, изменяющий свойства компонентов, вынести в отдельную процедуру. Чтобы в модуле unit2.pas можно было "видеть" форму, описанную в модуле unit1.pas и, наоборот, в операторы USES надо добавить ссылки модулей друг на друга. В файле unit1.pas в самом начале в конец списка модулей в операторе USES добавим unit2. А вот в файле unit2.pas так сделать не удастся – Delphi запрещает ситуацию, когда два модуля ссылаются друг на друга (так называемая "круговая ссылка"). Что же делать? Выход очень простой: ввести ссылку в раздел Implementation:
Implementation
uses unit1, Windows, Forms;
Как видно из примера, приходится вручную прописывать и ссылки на библиотечные модули Windows (в нем хранится функция RGB) и Forms (в нем хранится переменная Application).
Цикл смены цветов будет выполняться бесконечно, но надо предусмотреть возможность его останова при завершении работы программы. В модуле unit1 объявим глобальную переменную br:
var
Form1: TForm1;
Thread: Time;
br:BOOLEAN=false;
Здесь же объявим и переменную Thread, которая будет представлять дочерний поток в программе.
Переменная br должна устанавливаться в True при запросе пользователя на закрытие формы. Для этого создаем обработчик события формы OnCloseQuery:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
br:=true
end;
В модуле unit2 в класс Time добавляем заголовок нашей будущей процедуры:
type
Time = class(TThread)
private
{ Private declarations }
procedure Show; { эту строчку дописать }
protected
procedure Execute; override;
end;
Далее пишем саму процедуру Show:
procedure Time.Show;
var red, green, blue:byte;
begin
for red:=0 to 255 do
for green:=0 to 255 do
for blue:=0 to 255 do
begin
form1.Color:=RGB(Red,Green,Blue);
application.ProcessMessages;
if br then exit
end
end;
Оператор If обеспечивает выход из процедуры при закрытии программы. Осталось организовать синхронный вызов процедуры Show. В заготовке процедуры Execute пишем:
procedure Time.Execute;
begin
{ Place thread code here }
repeat
Synchronize(Show)
until terminated;
end;
Здесь цикл Repeat..Until вызывает метод Synchronize, который, в свою очередь, синхронно и корректно выполняет нашу процедуру Show. Цикл прекращается при завершении потока, когда его свойство Terminated устанавливается в True.
В модуле unit1 осталось организовать запуск потока и прекращение его работы. Пишем обработчик события создания формы OnCreate:
procedure TForm1.FormCreate(Sender: TObject);
begin
DoubleBuffered:=TRUE;
Thread:=Time.Create(false);
Thread.FreeOnTerminate:=true;
Thread.Priority:=tpLower
end;
Поток создается вызовом метода Create. Параметр False указывает на то, что поток начнет исполняться немедленно после создания. Свойство FreeOnTerminate, будучи установленным в True, автоматически освобождает занятую потоком память после завершения его работы. Свойство Priority указывает, какая доля процессорного времени отводится на выполнение потока. Возможны следующие варианты:
Табл. 22.1
Константа
| Описание
|
tpIdle
| Поток выполняется, только если приложение находится в режиме ожидания и ничего не делает
|
tpLowest
| Приоритет на два уровня ниже нормального
|
tpLower
| Приоритет на один уровень ниже нормального
|
tpNormal
| Нормальный приоритет
|
tpHigher
| Приоритет на один уровень выше нормального
|
tpHighest
| Приоритет на два уровня выше нормального
|
tpTimeCritical
| Наивысший приоритет
|
Установка приоритета прямо влияет на скорость выполнения потока. При установке высоких приоритетов поток начинает "тормозить" как основную программу, так и всю операционную систему.
При завершении работы программы необходимо завершить и поток. В обработчик события OnCLoseQuery допишем строчку Thread.Terminate:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
br:=true;
Thread.Terminate
end;
Готово! Запускаем программу и видим, как непрерывно меняется фон формы, не мешая при этом выполнять вычисления.