Среда программирования Delphi предоставляет программисту доступ к возможностям создания многопоточных приложений с помощью специального класса TThread.
С точки зрения операционной системы (ОС) поток – это ее объект. При создании он получает дескриптор и отслеживается ОС. Объект класса TThread – это конструкция Delphi, соответствующая потоку. Этот объект создается до реального возникновения потока в системе и уничтожается после его исчезновения. Другая отличительная черта класса TThread – это совместимость с библиотекой визуальных компонентов VCL.
Класс TThread включает в себя следующие элементы:
1. Constructor Create (Create Suspended: Boolean);
В качестве аргумента он получает параметр CreateSuspended. Если его зна¬чение равно True, вновь созданный поток не начинает выполняться до тех пор, пока не будет сделан вызов метода Resume. В случае, если CreateSuspended имеет значение False, поток начинает исполнение и конструктор завершается.
2. Destructor Destroy; override;
Деструктор Destroy вызывается, когда необходимость в созданном потоке отпадает. Деструктор завершает его и высвобождает все ресурсы, связанные с объектом TThread.
3. Procedure Resume;
Метод Resume класса TThread вызывается, когда поток возобновляется после остановки, или если он был создан с параметром create Suspended, равным True.
4. Procedure Suspend;
Вызов метода Suspend приостанавливает поток с возможностью повторного запуска впоследствии. Метод Suspend приостанавливает поток вне зависимости от кода, исполняемого потоком в данный момент; выполнение продолжается с точки останова.
5. Property Suspended: Boolean;
Свойство Suspended позволяет программисту определить, не приостановлен ли поток. С помощью этого свойства можно также запускать и останавливать поток. Установив suspended в True, будет получен тот же результат, что и при вызове метода Suspend — приостановку. Наоборот, установка Suspended в False возобновляет выполнение потока, как и вызов метода Resume.
6. Function Terminate: Integer;
Метод Terminate существует для окончательного завершения потока (без последующего запуска). Он останавливает поток и возвращает управление вызвавшему процессу только после того, как это произошло. Значение, возвращаемое функцией Terminate, соответствует состоянию потока. Примерами возможных состояний являются случай нормального завершения и случай, когда к моменту вызова Terminate поток уже завершился (или был завершен из другого потока) .
7. Property Terminated: Boolean;
Свойство Terminated позволяет узнать, произошел ли уже вызов метода Terminate или нет.
8. Function WaitFor: Integer;
Метод WaitFor предназначен для синхронизации потоков и позволяет одному потоку дождаться момента, когда завершится другой поток. Если внутри потока FirstThread имеется код:
Code := SecondThread.WaitFor;
то это означает, что поток FirstThread останавливается до момента завершения потока SecondThread. Метод WaitFor возвращает код завершения ожидаемого потока.
9. Property Handle: THandle read FHandle;
Property ThreadID: THandle read FThreadID;
Свойства Handle и ThreadID дают программисту непосредственный доступ к потоку средствами API Win 32. Если необходимо обратиться к потоку и управлять им, минуя возможности класса TThread, значения Handle и ThreadID могут быть использованы в качестве аргументов функций Win 32 API. Например, если перед продолжением выполнения приложения необходимо дождаться завершения сразу нескольких потоков, то следует вызвать функцию API WaitForMuitipleObjects. Для ее вызова необходим массив дескрипторов потоков.
10. Property Priority: TThreadPriority;
Свойство Priority позволяет запросить и установить приоритет потоков. Приоритет определяет, насколько часто поток получает время процессора. Допустимыми значениями приоритета являются tpIdie, tpLowest, tpLower, tpNormal, tpHigher, tpHighest и tpTimeCritical.
11. Procedure Synchronize (Method: TThreadMethod);
Этот метод относится к секции protected, то есть может быть вызван только из потомков TThread. Delphi предоставляет программисту метод synchronize для безопасного вызова методов VCL внутри потоков. Во избежание ситуаций гонок , метод synchronize дает гарантию, что к каждому объекту VCL одновременно имеет доступ только один поток. Аргумент, передаваемый в метод synchronize, — это имя метода, который производит обращение к VCL. Вызов Synchronize с этим параметром — это то же, что и вызов самого метода. Такой метод (класса TThreadMethod) не должен иметь никаких параметров и не должен возвращать никаких значений. Например, в основной форме приложения нужно предусмотреть функцию
procedure TMainForm.SyncShowMessage;
begin
ShowMessage(IntToStr(Listl.Count));
//другие обращения к VCL
end;
Тогда в потоке для показа сообщения следует писать не
ShowMessage(IntToStr(Listl.Count));
и даже не
MainForm.SyncShowMessage;
а только следующим образом:
Synchronize(MainForm.SyncShowMessage);
Производя любое обращение к объекту VCL из потока, следует убедиться, что при этом используется метод Synchronize; в противном случае результаты могут оказаться непредсказуемыми.
12. Procedure Execute; virtual; abstract;
Эта процедура является главным методом объекта TThread. В теле метода должен содержаться код, который представляет собой программу потока. Метод Execute класса TThread объявлен как абстрактный.
Переопределяя метод Execute, можно тем самым закладывать в новый потоковый класс то, что будет выполняться при его запуске. Если поток был создан с аргументом CreateSuspended, равным False, то метод Execute выполняется немедленно, в противном случае Execute выполняется после вызова метода Resume.
Если поток рассчитан на однократное выполнение каких-либо действий, то никакого специального кода завершения для него писать не надо. После выполнения метода Execute будет вызван деструктор, который сделает все необходимое.
Если же в потоке будет выполняться какой-то цикл, и поток должен завершиться вместе с приложением, то условия окончания цикла должны быть примерно такими:
procedure TMyThread.Execute;
begin
repeat
DoSomething;
until CancelCondition or Terminated end;
Здесь CancelCondition — условие завершения потока пользователя (исчерпание данных, поступление на вход того или иного символа и т. п.), а свойство Terminated говорит о завершении потока извне (скорее всего, завершается породивший его процесс).
С завершением потока следует быть очень внимательным, — если он зациклился и не реагирует на сигналы завершения, то зависнет все приложение.
13. Property ReturnValue: Integer;
Свойство ReturnValue позволяет узнать и установить значение, возвращаемое потоком по его завершении. Эта величина полностью определяется пользователем. По умолчанию поток возвращает ноль, но если программист захочет вернуть другую величину, то простое изменение свойства ReturnValue внутри потока позволит получить эту информацию другим потокам. Это, например, может пригодиться, если внутри потока в ходе его выполнения возникла какая-либо сложная ситуация.
Рассмотрим примеры создания простых многопоточных приложений.
П р и м е р 2. Создается приложение, выполняющее те же операции, что и в примере1.
Для создания приложения выполниту следующие действия:
1. Разместите на форме две строки редактирования, два регулятора и один компонент типа TTimer, как показано на рис. 2.
Рис. 2
2. Выполните команду File | Save Project As и сохраните модуль и проект, назначив им соответствующие имена.
3. Выполните команду File | New | Other. В открывшемся диалоговом окне New Items выполните двойной щелчок на объекте типа поток (значок Thread Object).
4. В диалоговом окне для именования объекта поток (рис. 3) введите TSimpleThread и нажмите ОК.
Рис. 3
Delphi создаст шаблон для нового потока, который показан далее:
unit Unitl;
interface
uses
Classes;
type
TSimpleThread= class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{Важно: методы и свойства объектов из состава VCL могут быть использованы посредством метода под названием Synchronize, например, Synchronize(UpdateCaption), где UpdateCaption может выглядеть так:
procedure TSimpleThread. UpdateCaption;
begin
Form. Caption: 'Updated in a thread';
end; }
( TSimpleThread }
procedure TSimpleThread. Execute;
Var
begin
{ Код потока помещается здесь }
end;
end. }
5. Измените объявление класса TSimpleThread, чтобы включить в секцию public поле Count. Поле count будет использовано, чтобы подсчитать, сколько вычислений в секунду выполняется в потоке:
TSimpleThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
public
Count: Integer;
end;
6. Изменения, вносимые в модуль Execute, как и в примере 1, заключаются в том, чтобы подсчитать среднее значение десяти случайных чисел и затем увеличить на единицу значение count. Эти изменения показаны ниже:
procedure TSimpleThread.Execute;
Var
I, Total, Avg: Integer;
begin
While True Do
Begin
Total: =0;
For I:=1 To 10 Do
Inc(Total, Random(MaxInt));
Avg:=Avg Div 10;
Inc(Count );
End;
end;
7. Выполните команду File | Save As… и сохраните модуль с потоком как Thrd.pas.
8. Отредактируйте главный файл модуля и добавите модуль Thrd к списку используемых модулей. Он должен выглядеть так:
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Thrd, ExtCtrls, StdCtrls, ComCtrls;
9. В секции public формы TForm1 добавьте следующую строку:
Thread1, Thread2: TSimpleThread;
10. Выполните двойной щелчок на свободном месте рабочей области формы, чтобы объявить два потока, которые будут использоваться программой; при этом создастся шаблон метода FormCreate. В этом методе произойдет создание потоков, присвоение им приоритетов и запуск. Поместите в шаблон FormCreate следующий код:
procedure TForm1.FormCreate(Sender: TObject);
begin
Thread1:=TSimpleThread.Create(False);
Thread1.Priority:=tpLowest;
Thread2:=TSimpleThread.Create(False);
Thread2.Priority:= tpLowest;
end;
11. Выполните двойной щелчок на компоненте TTimer для создания пустого шаблона метода Timer. Этот метод будет автоматически вызываться каждую секунду, чтобы приложение могло отслеживать состояние потоков. Метод Timer должен выглядеть следующим образом:
procedure TForm1.Timer1Timer (Sender: TObject);
begin
Edit1.Text:=IntToStr(Thread1.Count );
Edit2.Text:=IntToStr(Thread2.Count );
Thread1.Count: =0;
Thread2. Count:=0;
end;
12. Выполните щелчок на левом регуляторе (TrackBar1) и выберите страницу Events в окне Object Inspector. Выполните двойной щелчок напротив имени метода OnChange для создания шаблона метода, который будет вызываться каждый раз при изменении положения регулятора. Метод будет устанавливать регулятор в соответствии с приоритетом потока. Он должен содержать следующий код:
procedure TForm1.TrackBar1Change (Sender:TObject);
Var
I: Integer;
Priority: TThreadPriority;
begin
Priority:=tpLowest;
For I: = 0 To (Sender as tTrackBar).Position - 1 Do
inc(Priority);
if Sender = TrackBar1
Then Thread1.Priority:=Priority
Else Thread2.Priority:=Priority;
end;
13. Чтобы связать метод, созданный на шаге 12, со вторым регулятором, следует выбрать в окне Object Inspector TrackBar2, открыть комбинированный список события OnChange и выбрать метод TrackBar1Change.
14. Чтобы ограничить приоритет потоков, значением не выше, чем tpHigher, максимальное положение регуляторов должно быть ограничено четырьмя. Для этого выберите TrackBar1, и затем, удерживая клавишу , выберите TrackBar2. Когда оба компонента будут выделены, откройте в окне Object Inspector страницу Properties и установите для свойства Max значение 4.
15. Запустите подготовленное приложение на выполнение. Изменяя приоритеты, проанализируйте их влияние на производительность одновременно выполняющихся потоков.
Сноски:
1 Метод Terminate автоматически вызывается и из деструктора объекта. В явном виде его за редким исключением, вызывать не надо.
2 Следует с осторожностью использовать приоритеты tpHighest и tpTimeCritical. Оба они могут оказать влияние на выполнение приложения, а последний — и на всю операционную систему.
3 Ситуация гонок возникает, когда два или более потока пытаются получить доступ к общему ресурсу и изменить его состояние.
|