Потоки в Delphi

advertisement
Потоки в Delphi
Потоки в Delphi выполняют функцию имитации
псевдопараллельной работы приложения. Как известно,
для организации многозадачности операционная система
выделяет каждому приложению, выполняющемуся в
настоящий момент, определённые кванты времени, длина
и количество которых определяется его приоритетом.
Поэтому объём работы, который приложение может
выполнить, определяется тем, сколько таких квантов оно
сможет получить в единицу времени. Для операционной
системы каждый поток является самостоятельной
задачей, которой выделяются кванты времени на общих
основаниях. Поэтому приложение Delphi, умеющее
создать несколько потоков, получит больше времени
операционной системы, и соответственно сможет
выполнить больший объём работы.
Потоки в Delphi
Создать дополнительный поток в Delphi поможет объект
TThread. Ввести объект TThread в программу можно двумя
способами:
с помощью Мастера;
вручную.
1. Мастер создания дополнительного потока в Delphi
создаёт отдельный модуль, в рамках которого
выполняется поток. Выполним:
File -> New -> Other...
В появившейся табличке выбора найдём TThread Object.
Появится окошко, в верхнюю строку которого (Class
Name) введём имя нашего будущего потока: MyThread. В
результате будет создан модуль, содержащий заготовку
кода, реализующего дополнительный поток Delphi
Потоки в Delphi
unit Unit2; // Имя модуля, содержащего поток. При сохранении его можно изменить.
Interface
uses
Classes;
type
MyThread = class(TThread) //MyThread - имя потока.
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ Important: Methods and properties of objects in visual components can only be
used in a method called using Synchronize, for xample,
Synchronize(UpdateCaption);
and UpdateCaption could look like,
procedure MyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ MyThread }
procedure MyThread.Execute;
begin
{ Place thread code here }
end;
end.
Потоки в Delphi
В первом способе класс MyThread был создан мастером в дополнительном
модуле. Второй способ состоит в том, что мы сами создаём такой класс в
рамках одного из уже существующих модулей программы, например, в
модуле Unit1:
unit Unit1; //Обычный модуль в котором описывается основная программа
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
//Здесь необходимо описать класс TMyThread:
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
Потоки в Delphi
var
Form1: TForm1;
//Нужно ввести переменную класса TMyThread
MyThread: TMyThread;
implementation
{$R *.dfm}
//Нужно создать процедуру Execute, уже описанную в классе TMyThread
procedure TMyThread.Execute;
begin
//Здесь описывается код, который будет выполняться в потоке
end;
Потоки в Delphi
Если поток создаётся мастером, т.е. в другом модуле, то не забудьте в
основном модуле описать переменную - экземпляр потока. Также, поскольку
класс потока описан в другом модуле, имя этого модуля необходимо
добавить в секцию uses. Теперь можно запускать поток, даже если в его
процедуре Execute нет ни единого оператора.
//Запускать поток будем нажатием на кнопку:
procedure TForm1.Button1Click(Sender: TObject);
begin
//Вначале нужно создать экземпляр потока:
MyThread:=TMyThread.Create(False);
//Параметр False запускает поток сразу после создания, True - запуск
впоследствии , методом Resume
//Далее можно указать параметры потока, например приоритет:
MyThread.Priority:=tpNormal;
end;
end.
Потоки в Delphi
Применение потоков
Если в основной программе попробовать выполнить такой цикл:
while True do;
то приложение зависнет. А теперь поместите его в процедуру Execute. При
нажатии на кнопку наш бесконечный цикл будет непрерывно выполняться в
потоке, однако и приложение как целое не зависнет.
procedure TMyThread.Execute;
begin
while True do;
end;
В предыдущем примере поток выполняет бесконечный цикл. Однако, поток
также обладает возможностями, позволяющими из основной программы
передать ему приказ прекратить работу.
Потоки в Delphi
Метод потока Terminate устанавливает свойство Terminated потока в
True. Анализируя это свойство, поток может понять, что он должен
завершить работу. Пример:
procedure TMyThread.Execute;
begin
while True do
if MyThread.Teminated then break;
end;
Этот код выполняет бесконечный цикл. Однако, при выполнении в основной
программе оператора
MyThread.Terminate;
цикл завершается, и поток прекращает свою работу.
Потоки в Delphi
При работе с потоками необходимо учитывать приоритет создаваемых
потоков. Так, если в предыдущем примере запустить не один поток, а два или
больше, нажав на кнопку несколько раз, то компьютер станет очень заметно
"тормозить". Это происходит потому, что приоритет по умолчанию новых
потоков - нормальный. Можно уменьшить его, задав
MyThread.Priority:=tpLower;
Этого достаточно, чтобы компьютер чувствовал себя более свободно.
• tpIdle Низший приоритет. Поток получает время только тогда, когда
операционая система находится в состоянии простоя.
• tpLowest Приоритет на два пункта ниже нормального
• tpLower Приоритет на один пункт ниже нормального
• tpNormal Нормальный приоритет
• tpHigher Приоритет на один пункт выше нормального
• tpHighest Приоритет на два пункта выше нормального
• tpTimeCritical Максимальный приоритет. Приоритет на уровне функций
ядра операционной системы.
Потоки в Delphi
При использовании в приложении нескольких потоков необходимо гарантировать, что
в данный момент только один из потоков может иметь доступ к свойствам и методам
объекта VCL - визуального компонента Delphi, то есть действия потоков необходимо
синхронизировать между собой. Для выполнения такой синхронизации в Delphi
применяется специальный метод Synchronize, в рамках которого и нужно вызывать
процедуры, модифицирующие свойства визуальных компонентов.
Процедура Synchronize использует в качестве параметра те процедуры, в которых
происходит модификация свойств визуальных компонентов, и блокирует
одновременный доступ к компоненту нескольких потоков. Вот какой пример, в
частности, содержится в модуле, сгенерированном Мастером создания потока:
{Важно: Методы и свойства объектов в визуальных компонентах могут
вызываться
только в методе Synchronize, например:}
procedure MyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end;
procedure MyThread.Execute;
begin
Synchronize(UpdateCaption);
end;
Потоки в Delphi
В данном случае поток используется для изменения заголовка Формы.
Изменение заголовка происходит в процедуре UpdateCaption.
Казалось бы, для изменения заголовка эту процедуру достаточно
вызвать в основной процедуре потока, Execute. Однако, если несколько
таких потоков в программе одновременно попытаются изменить
заголовок Формы, то это может привести к непредсказуемым
последствиям. Для исключения этого процедура UpdateCaption
вызывается в процедуре Execute как параметр метода Synchronize.
Нужно знать, что метод Synchronize выполняется в главном потоке
приложения. Поэтому, работая с несколькими потоками в приложении и
применяя метод Synchronize, нужно учитывать, что:
во-первых, частый вызов Synchronize тормозит выполнение
приложения;
во-вторых, если практически все процедуры выполняющегося потока
выполняются с использованием метода Synchronize, то смысла в
создании такого потока нет - всё равно его работа пройдёт в главном
потоке.
Потоки в Delphi
Как пример рассмотрим всё ту же модификацию заголовка Формы. Пусть в
одном из потоков происходит работа с большим массивом, и требуется
отображать какой объём массива уже обработан. Для этого организуем
поток, который будет выполнять эту работу. Будем выводить в заголовок
Формы индекс элемента, с которым обрабатывающий поток работает в
данный момент. Делать это будем с периодичностью 10 раз в секунду.
Сначала сделаем так:
procedure TMyThread.UpdateCaption;
begin
while True do
begin
Form1.Caption:=IntToStr(I);//I - глобальная переменная основной
//программы, индекс массива
sleep(100);
end;
end;
procedure TMyThread.Execute;
begin
Synchronize(UpdateCaption);
end;
Потоки в Delphi
Видим, что происходит именно то, о чём написано выше. Так как весь код
потока, и модификация заголовка Формы, и цикл ожидания, выполняется в
методе Synchronize, а значит в главном потоке, то приложение будет
выглядеть зависшим, и его даже будет невозможно корректно завершить.
Теперь попробуем вывести цикл за пределы Synchronize:
procedure TMyThread.UpdateCaption;
begin
Form1.Caption:=IntToStr(Cap);
end;
procedure TMyThread.Execute;
begin
while True do
begin
Synchronize(UpdateCaption);
sleep(100);
end;
end;
Это правильный вариант. С помощью метода Synchronize выполняется
только непосредственная модификация Заголовка Формы, а цикл ожидания
выполняется в потоке, и не мешает главному потоку.
Потоки в Delphi
Некоторым объектам VCL процедура Synchronize не требуется, так как они
всё же умеют корректно работать с потоками, либо нуждаются в других
методах синхронизации.
Так, корректно работают с потоками
• компоненты доступа к базам данных (с использованием компонентов
класса TSession). Исключение составляют базы данных Microsoft Access;
• классы, которые работают непосредственно с графикой. Это TFont, TPen,
TBrush, TBitmap, TMetafile и TIcon. Канву объектов этих классов
(Canvas) можно использовать не применяя метод Synchronize. Это
делается с помощью блокировки канвы. То есть, поток, использующий в
данный момент канву, предварительно блокирует канву методом Lock,
препятствующим другим потокам работать с канвой, и разблокирует затем
методом UnLock.
Download