Глава 14
ОСОБЕННОСТИ
РАЗРАБОТКИ МОДУЛЕЙ-ПОТОКОВ
Современные операционные системы
Windows 32 обеспечивают не только многозадачность, т. е. возможность параллельной
работы нескольких программ, но и многопоточность, когда в рамках одной программы
организуется несколько параллельно выполняемых фрагментов (потоков), каждый
из которых конкурирует с другими потоками за наиболее важный ресурс - время
центрального процессора. В многопоточном режиме время ЦП выделяется для каждого
процесса небольшими порциями (квантами), по истечении этого времени управление
передается другому потоку и т. д. до тех пор, пока потоки не закончат свою работу.
В любой работающей программе организуется как минимум один поток для команд
программы. С помощью объектов класса TThread программа может создать дополнительные
потоки для проведения некоторой фоновой работы (например, текстовый процессор
Word создает
дополнительные
потоки для проверки правильности орфографии, разбивки на страницы, печати документа
и т. п.).
Для создания дополнительного потока
в программах Delphi предназначен специальный модуль потока в репозитории он
обозначен пиктограммой Thread Obiecll). При выборе этого модуля Delphi запрашивает
имя класса, который будет дочерним для основополагающего класса TThread. Необходимость
наследования связана с тем, что класс TThread содержит абстрактный метод Execute,
который, собственно, и должен исполняться в рамках нового потока и который,
следовательно, обязан перекрываться в потомках.
После указания имени дочернего класса
Delphi раскрывает дополнительный модуль с обширным комментарием и заготовкой
для дочернего класса.
Например (с соответствующим переводом):
unit
Unit1;
interface
uses
Classes;
type
MyThread
=
class
(TThread)
private
{ Private declarations
}
protected
procedure
Execute;
override;
end;
implementation
{ Важно: Методы и свойства
объектов из библиотеки визуальных компонентов могут использоваться только
в рамках вызова метода Synchronize, например:
Synchronize(UpdateCaption);
где метод UpdateCaption
должен быть подобен такому
procedure MyThread.UpdateCaption;
begin
Formi.Caption :=
'Новый текст метки';
end;
}
( MyThread }
procedure
MyThread.Execute;
begin
{ Пожалуйста, поместите
код потока в этом месте }
end;
end.
Программирование потока ничем не
отличается от программирования обычной программы за одним важным исключением:
поток не должен использовать методы и свойства визуальных компонен тов, которые
приводят к изменению внешнего вида программа Точнее, он может это делать только
при обращении к специальному методу synchronize, с помощью которого осуществляется
синхронизация исполнения главного потока программы с дополнительным потоком.
Для иллюстрации приемов работы с
потоком создадим программу, которая будет непрерывно обновлять содержимое многострочного
редактора и при этом осуществлять математические вычисления.
Для ее создания сначала на пустую
форму поместите панель TPpanel, очистите ее свойство caption и поместите в Align
значение аlRight - эта панель предназначена для размещения редактора TSpinEdit,
кнопки TButton и индикатора TGauge и всегда должна располагаться в правой части
окна программы. Поместите на панель перечисленные компоненты так, как это показано
на рисунке (компоненты TSpinEdit и TGuage находятся на странице samples палитры
компонентов).
Установите в свойство SpinEditl.Value
3начение 2, присвойте свойству Gaugel. Kind значение gkPie, Gaugel. BorderStyle-bsNone
и Button1.Caption — 'Квадрат'.
На свободное место формы положите
компонент TMemo и установите для него в свойство Align значение alСlient, а
свойство Name- 'mmOutput'.
Теперь создадим обработчик события
Button1.Click: при нажатии на кнопку вначале содержимое редактора SpinEdit1
возводится в квадрат до тех пор, пока отображаемое в нем значение не слишком
большим (больше 10
+1233
). В этот момент надпись на кнопке меняется
на “корень”, а нажатие на нее вычисляет корень квадратный ИЗ величины SpinEdit1.
Дважды щелкните по кнопке Button1и
напишите такой код:
procedure
TForm1.Button1Click(Sender: TObject) ;
begin
if Tag=0
then
begin
SpinEditl.Text := Float-ToStr(sqr(StrToPloat(SpinEditl.Text)))
;
if StrToFloat(SpinEditl.Text)
> 1el233
then
begin
Tag
:=
1;
Buttoni.Caption := 'Корень'
end
end
else
begin
SpinEditl.Text := FloatToStr(sqrt(StrToFloat(SpinEditl.Text)))
;
if
StrToFloat(SpinEditl.Text)
<
2
then
begin
SpinEditl.Value
:=
2;
Tag := 0;
Button1.Caption := 'Квадрат'
end
end
end;
Таким образом, главный код программы
связан с извлечением корня или возведением в квадрат величины, записанной в
редакторе
SpinEditl.
Теперь создадим модуль потока, в
методе Execute которого будем непрерывно формировать по 100 строк в редакторе
mmOutput и показывать процент заполнения редактора с помощью индикатора Gaugel.
Выберите пиктограмму модуля потока
в окне репозитория Delphi и дайте наследнику класса Thread имя ThreadDemo. Окончательный
текст модуля потока представлен ниже.
unit
Unit2;
interface
uses
Classes;
type
ThreadDemo =
class
(TThread)
private
{ Private declarations
}
protected
S
: String;
N: Integer;
procedure
UpdateMemo;
procedure
UpdateGauge;
procedure
Execute;
override;
end;
var
TDemo: ThreadDemo;
implementation
uses
Uniti,SysUtils;
Important: Methods and
properties of objects in VCL can only
re used .in a
method called using
Synchronize, for example,
Synchronize(UpdateCaption);
and UpdateCaption could
look like,
procedure ThreadDemo.UpdateCaption;
begin
Formi.Caption :=
'Updated in a thread';
end; }
ThreadDemo }
procedure
ThreadDemo.Execute;
var
j, k: Integer;
begin
repeat
S:='';
Synchronize(UpdateMemo);
for
k := 0
to
99
do
begin
N := k;
S
: = ' ' ;
for
j := 1
to
20
do
S := S+FormatFloat('00',k),
Synchronize(UpdateMemo) ;
Synchronize(UpdateGauge)
end;
until
False
end;
Procedure
ThreadDemo.UpdateMemo;
begin
with
.Form1.mmOutput.Lines
do
if S=' ' then
Clear
else
Add(S)
end;
Procedure
ThreadDemo.UpdateGauge;
begin
Form1.Gaugel.Progress
:=
N
end;
end.
Если вы запустите таким способом
подготовленную программу, то ничего не произойдет - ведь мы еще не запустили
поток. Чтобы сделать это, добавьте в модуле Unit1 главной формы ссылку uses
Unit1, раскройте в окне Инспектора объектов список компонентов, выберите компонент
Form1 и на его странице Event дважды щелкните по свойству onActivate, чтобы
создать такой обработчик этого события:
procedure
TForm1.FormActivate(Sender: TObject);
begin
TDemo := ThreadDemo.Create
(False),
end;
Вот так просто запускается дополнительный
поток - мы инициируем объект TDemo, передавая в его Консруктор ThreadDemo.Create
единственный параметр False (этот
параметр показывает, должен ли вновь созданный поток “спать” - True или он обязан
немедленно начать работу - False). Программа в любой момент может приостановить
работу потока, присвоив его свойству suspended значение True, и продолжить его
выполнение, присвоив этому свойству значение False. Обратите внимание - метод
Execute потока вынесен в секцию protected и поэтому недоступен из основного
модуля. Выполнение этого метода начинается автоматически, как
только
свойство suspended примет значение False.
Для обращения к свойствам и методам
визуальных компонентов формы Form1 предназначен специальный метод потока Synchronize.
Единственным параметром обращения к этому методу должно быть имя любой потоковой
процедуры без параметров. Внутри такой процедуры разрешается обращаться к методам
и свойствам визуальных компонентов. В нашем потоке две такие процедуры - UрdateMemo
и updateGuage. В первой строка s добавляется к содержимому редактора mmoutput,
во втором - глобальная переменная n присваивается свойству progress индикатора
Gauge1. Поскольку эти процедуры не могут иметь параметров, для управления их
работой приходится использовать глобальные переменные S и N.