Как известно, Windows – многозадачная операционная система, обеспечивающая одновременное выполнение многих приложений. Одновременность эта на самом деле кажущаяся. В один момент времени процессор все равно может выполнять только одну команду (для настоящей многозадачности нужны многопроцессорные системы). Просто процессор очень быстро переключается между запущенными в системе задачами, создавая иллюзию их одновременной работы. Подобное псевдоодновременное выполнение процессов известно как "операционная система с разделением времени" (time-sharing).
Для решения многих задач было бы удобно в рамках одного приложения запускать несколько процессов, работающих параллельно. Например, пока программа выполняет какие-то сложные и долгие расчеты в одном процессе, второй процесс, чтобы попусту не терять время, выполняет какие-либо действия с пользовательским интерфейсом (скажем, строит график). При этом надо понимать, что запуск дополнительных процессов снижает скорость работы основного процесса. В приведенном примере расчет будет выполняться медленнее, но это компенсируется выигрышем в общем времени работы.
Другой пример – программа тестирования знаний, которая дает студенту ограниченное время на выполнение тестов и по истечении этого времени выполняет какие-либо действия (обычно вывод сообщения "Зачета нет и не будет").
Наконец, параллельные процессы могут использоваться для изображения сложной анимации, когда надо одновременно перемещать несколько объектов.
Для организации параллельных процессов в 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;
Готово! Запускаем программу и видим, как непрерывно меняется фон формы, не мешая при этом выполнять вычисления.