- •Windows -приложение
- •Среда программирования
- •Встроенный отладчик
- •Использование графики
- •Графические данные и палитра
- •Сохранение проекта
- •Структура приложения
- •Структура модуля
- •Простые типы
- •Символьные типы
- •Логические типы
- •Тип перечень
- •Составной оператор
- •Оператор if
- •Оператор цикла for
- •Оператор цикла while
- •Оператор цикла repeat
- •Пример приложения 6
- •Пример приложения 7
- •Статические массивы
- •Динамические массивы
- •Оператор with
- •Идентичность типов
- •Совместимость типов
- •Преобразование типов
- •Операторы обработки исключительных ситуаций
- •Рекурсия
- •Процедура exit
- •Директивы подпрограммы
- •Класс как объектный тип
- •Наследование
- •Операции is и as
- •Типы ссылки на класс
- •Типизированные файлы
- •Файлы без типа
- •Пример приложения 17
- •Компонент tmainmenii
- •Двунаправленные списки
- •Потоки данных
- •Пример приложения 22
- •Интерфейс drag and drop
- •Пример приложения 24
- •С файлами
- •Пример приложения 26
- •Программные потоки
- •Приоритеты потоков
- •Класс tthread
- •Проблемы синхронизации потоков
Класс tthread
Для реализации потоков Delphi предоставляет класс TThread. Наиболее важными методами являются Execute и Synchronize. Первый метод осуществляет выполнение потока. Этот метод абстрактный. Поэтому необходимо создать класс, который является потомком TThread, и в этом классе переопределить метод Execute. Второй метод Synchronize, чтобы избежать одновременного использования компонентов приложения, сообщает основной подзадаче, что требуется обращение к ресурсам, которые, возможно, заняты. При этом передается адрес метода, который основная подзадача может вызвать, чтобы выполнить требуемую потоком работу. Процесс извещения является последовательным и основная подзадача должна полностью завершить выполняемую работу, прежде чем получить следующее извещение. Извещающее сообщение отсылается при помощи подпрограммы Windows SendMessage. Результатом является то, что подзадача будет заморожена вплоть до полной отработки задания внутри основной подзадачи.
В программе перед выполнением потока, являющегося классом, естественно, должен быть построен соответствующий объект. Для этого вызывается метод create, имеющий один аргумент CreateSuspended типа Boolean. Если поток создан со значением аргумента CreateSuspended = false, то он немедленно (как только получит время процессора в соответствии с его собственным приоритетом и приоритетом процесса) начинает выполняться (запускается метод Execute), в противном случае Execute запускается вы-
214
зовом метода Resume. Внутри метода Execute могут выполняться некоторые циклические действия, для завершения которых можно воспользоваться свойством Terminated. Устанавливая значение этого свойства равным true внутри метода Execute или извне, поток получает команду о завершении его работы. С помощью метода Suspend можно приостановить работу потока и затем продолжить, вызывая метод Resume. Переустанавливая значение свойства Priority, можно управлять приоритетом потока.
ПРИМЕР ПРИЛОЖЕНИЯ 27
Рассмотрим простой пример по использованию одного дополнительного потока. Пусть некоторый поток с помощью собственного метода, например, Paint применяется для закраски некоторой области прямоугольной формы. Основной поток приложения создается для рисования окружностей внутри той же области с помощью обработчика OnMouseDown.
Для построения приложения необходимо создать проект с двумя модулями. Один модуль - основная программа с объявленной переменной типа поток в секции Public внутри создавемого класса формы, другой модуль -модуль потока. Поместим на форму две кнопки TButton. Создадим для них два обработчика OnClick. Для формы, как уже отмечено, создается обработчик OnMouseDown. Далее создадим модуль для реализации вспомогательного потока (основной поток автоматически создается для процесса (приложения)).
Необходимо открыть меню File - New и выбрать пункт Other. Откроется диалоговое окно, в котором нужно выбрать (дважды щелкнуть мышкой) значок Thread Object. Когда появится диалоговое окно, необходимо ввести имя класса TThread (Class Name = TPaintThread) и нажать на кнопку ОК. Ниже приводится весь программный код данного примера, который демонстрирует использование метода Synchronize.
unit MyThreadl; {Модуль для определения потока}
interface
uses Classes;
type
TPaintThread = class(TThread)
private
x,у:integer;
protected
procedure Execute; override; procedure Paint;
end; implementation
uses Unit27_1, Graphics; procedure TPaintThread.Execute; begin
215
randomize; Repeat
x:=random(250);
y:=random(Form1.ClientHeight); Synchronize(Faint) ;
until Terminated; end;
procedure TPaintThread.Paint; begin
Form1.Canvas.Pixels[x,y]:=clPurple; end; end.
SysUtils, Variants, Classes, Forms, Dialogs, StdCtrls, ExtCtrls,
unit Unit27_1; {Основной модуль} interface
uses Windows, Messages, Graphics, Controls, Buttons, MyThreadl ;
type
TForm1 = class(TForm) Button1: TButton; Button2: TButton; BitBtnl: TBitBtn;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button:
TMouseButton; Shift: TShiftState; X, Y: Integer);
public
MyPaintThread:TPaintThread;
end;
var Form1: TForm1; implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject); begin
Button1,Enabled:=false;
Button2.Enabled:=true;
MyPaintThread:=TPaintThread.Create(false);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
MyPaintThread.Free;
Button1.Enabled := True;
Button2.Enabled := False; end; procedure TForm1.FormMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin
Canvas.Pen.Color := Color;
216
Canvas.Brush.Color := Color;
Canvas.Ellipse (x - 30, у - 30, x + 30, у + 30);
end; end.
Следует обратить внимание на строку MyPaintThread:TPaintThread; и использование модуля Graphics. Среда Delphi может не добавить этот модуль в список Dses. На рис. 63 приводится вариант решения данного примера.
Рис. 63
ИСПОЛЬЗОВАНИЕ БЛОКИРОВКИ В ПРИМЕРЕ 27
Можно написать другой вариант решения данного примера, воспользовавшись особенностью компонента TCanvas. Дело в том, что этот компонент содержит два метода Lock и UnLock, которые позволяют блокировать вывод на канву с последующей разблокировкой. Естественно, при использовании этих методов канвы отпадает необходимость синхронизации работы потоков с помощью метода Synchronize.
Код программы приводится далее. Из текста кода основного модуля приводится только один обработчик TForm1. FormMouseDown, в который внесены изменения. Отметим, что код потока немного упростился.
unit MyTread2; interface
uses Classes; type
217
TPaintThread = class(TThread)
protected
procedure Execute; override;
end; implementation
uses Unit27_2, Graphics; procedure TPaintThread.Execute;
var x,у:integer; begin
randomize; Repeat
x:=random(250);
y:=random(Form1.ClientHeight); with Form1.Canvas do begin
Lock;
try
Pixels[x,y]:=clPurple; finally
UnLock;
end; end;
until Terminated; end;
end.
unit Unit27_2;
…………………….
procedure TForm1.FormMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer), begin
Canvas.Lock; try
Canvas.Pen.Color := Color; Canvas.Brush.Color := Color;
Canvas.Ellipse (x - 30, у - 30, x + 30, у + 30); finally
Canvas.Unlock;
end;
end;
end.
Нельзя не заметить, что второй вариант примера работает значительно быстрее. Но, к сожалению, не для любых потоков можно применить подобные приемы блокировки.
МНОГОПОТОЧНОЕ ПРИЛОЖЕНИЕ В ПРИМЕРЕ 28
В данном примере создаются три потока, которые закрашивают разными цветами один и тот же прямоугольник, как и в примере 27. Однако способ
218
закраски в примере 28 отличается тем, что используется сканирование прямоугольной области. В варианте, который приводится ниже, используется встроенная в класс TThread система безопасности с помощью метода синхронизации. Следует отметить, что данный подход не спасает приложение и компьютер от зависания, поэтому этот вариант примера лучше запускать из среды Delphi, чтобы можно было воспользоваться клавишами Ctrl+F2. Правда, вероятность зависания зависит от установленных приоритетов для каждого потока.
В примере используются новые компоненты: TTrackBar - для изменения приоритета потоков и TProgressBar - для визуального контроля скорости работы того или иного потока. Компонент TTrackBar применяется, когда требуется изменять значения в заданном диапазоне. Текущее значение определяется свойством Position, диапазон значений задается свойствами Min и Мах. В примере задано Min=0, Max=3. Второй компонент (TProgressBar на странице Win32 - индикатор хода работ) показывает графическую полосу хода выполнения какой-либо операции. Текущая позиция индикатора определяется свойством Position, свойства Min и Мах задают диапазон индикатора. В примере задано Min=0, Max определяется свойством формы ClientHeight, т.е. в данном случае числом дисплейных строк. Число участков, на которое делится весь диапазон, задано равным 10. Это число определяет величину шага сканирования диапазона, которое записывается в свойстве Step. Ниже приводится программный код примера. Отметим, что в обработчике TrackBar1Change используются свойства Tag компонентов TtrackBar, которые соответственно необходимо с помощью инспектора объектов установить равными 1, 2, 3.
unit MyThread3;
interface
uses Classes, Graphics, ComCtrls;
type
TPaintThread = class(TThread) private
x,у:integer; FColor:TColor; FProgressBar:TProgressBar; public
constructor InitColor(nColor:TColor);
property ProgressBar:TProgressBar write FProgressBar; procedure DisplayProgress; protected
procedure Execute; override; procedure Paint; end;
implementation uses Unit28;
219
Fcolor:=nColor;
end;
procedure TPaintThread.DisplayProgress;
begin
FprogressBar.Position:=y;
end;
procedure TPaintThread.Paint;
var j: Integer;
begin
for j:=0 to 250 do begin x: = j ;
Form1.Canvas.Pixels[x,y]:=FColor;
end;
end;
procedure TPaintThread.Execute;
var i: Integer;
begin
repeat
for i:=0 to Form1.ClientHeight do begin
y:=i;
Synchronize(DisplayProgress);
Synchronize(Paint);
end;
until Terminated;
end;
end.
unit Unit28;
interface uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, ComCtrls, MyThread3;
Type TForm1 = class(TForm)
TrackBar1: TTrackBar;
TrackBar2: TTrackBar;
ТгаскВагЗ: TTrackBar;
BitBtnl: TBitBtn;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
CheckBox3: TCheckBox;
ProgressBar1: TProgressBar;
ProgressBar2: TProgressBar;
ProgressBar3: TProgressBar;
procedure CheckBox1Click(Sender: TObject);
procedure TrackBar1Change(Sender: TObject);
220
procedure FormCreate(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); public
PT: array [1..3] of TPaintThread;
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Form1.Canvas.Lock;
if (Sender as TCheckbox).Checked then
PT [(Sender as TCheckbox).Tag].Resume
else PT [(Sender as TCheckbox).Tag].Suspend;
Form1.Canvas.UnLock;
end;
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
PT [(Sender as TTrackBar).Tag].Priority :=
TThreadPriority ((Sender as TTrackBar).Position);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
PT [1] := TPaintThread.Create (true); PT [2] := TPaintThread.Create (true); PT [3] := TPaintThread.Create (true);
PT[1].InitColor(clRed);
PT[2].InitColor(clBlue);
PT[3].InitColor(clGreen);
ProgressBar1.Max:=Form1.ClientHeight;
ProgressBar2.Max:=Form1.ClientHeight;
ProgressBar3.Max:=Form1.ClientHeight;
ProgressBar1.Step:=Form1.ClientHeight div 10;
ProgressBar2.Step:=Form1.ClientHeight div 10;
ProgressBar3.Step:=Form1.ClientHeight div 10;
PT[1].ProgressBar:=ProgressBar1;
PT[2].ProgressBar:=ProgressBar2;
PT[3].ProgressBar:=ProgressBar3 ;
end;
procedure TForm1.FormMouseDown(Sender: TObj ect;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer); begin Canvas.Lock;
try
Canvas.Pen.Color := Color;
Canvas.Brush.Color := Color;
221
Canvas.Ellipse (x - 30, у - 30, x + 30, у + 30); finally
Canvas.Unlock;
end; end; end.
Результат решения примера приводится на рис. 64 (показаны установки приоритетов, обеспечивающие достаточно надежную работу приложения).
Рис. 64
В примере создается массив рт из трех потоков. Цвет закраски прямоугольной области задается с помощью вызова конструктора initcolor. Разделение общих ресурсов, используемых потоками, обеспечивается методом Synchronize. Для ускорения работы синхронизируется работа не каждого доступа к канве по изменению цвета пиксела (Pixels [x,y]), а только очередной дисплейной строки. Можно попытаться синхронизировать доступ к каждому пикселу, возможно, это обеспечит более надежную работу (но замедлится работа приложения). Надежной работы данной программы можно добиться, используя методы блокировки канвы, как это было применено в примере 27. Далее для этого случая приводится код модуля, в котором объявлен поток (код основного модуля не изменился по сравнению с предыдущим примером).
unit MyThread3_1;
interface
uses Classes, Graphics, ComCtrls;
222
type
TPaintThread = class(TThread)
private FColor:TColor; FProgressBar:TProgressBar;
public
constructor InitColor(nColor:TColor);
property ProgressBar:TProgressBar write FProgressBar; protected
procedure Execute; override; end;
implementation
uses Unit28_1;
constructor TPaintThread.InitColor(nColor:TColor);
begin
Fcolor:=nColor;
end;
procedure TPaintThread.Execute;
var x,y: Integer; begin
repeat
for y:=0 to Form1.ClientHeight do begin FprogressBar.Position:=y; Form1.Canvas.Lock;
try
for x:=0 to 250 do
Form1.Canvas.Pixels[x,y]:=FColor; finally
Form1.Canvas.UnLock; end; end;
until Terminated; end;
end.
Единственное, что можно отметить для данного варианта, это то, что наблюдается некоторое притормаживание, когда включается в работу основной поток - рисование круга (или происходит нажатие на кнопку выхода из приложения). Это связано с тем, что блокируется не одна операция Pixels [х,у], а весь цикл закраски пикселов в данной строке.