Главная » Delphi » Объект TThread

0

Фактически Delphi  инкапсулирует в объекте Object  Pascal TThread объект потока API. Хотя класс TThread инкапсулирует практически все важнейшие функции объек та потока API в одном  едином  объекте, возможны ситуации, когда придется непо средственно обращаться к функциям API. Чаще всего это будет связано с необходимо стью синхронизации потоков. В настоящем разделе обсудим работу  объекта TThread и его применение в создаваемых приложениях.

Принципы работы объекта TThread

Класс TThread находится в модуле Classes и определен следующим образом:

TThread = class private

FHandle: THandle;

{$IFDEF MSWINDOWS}

FThreadID: THandle;

{$ENDIF}

{$IFDEF LINUX}

// ** FThreadID не соответствует THandle в Linux **

FThreadID: Cardinal;

FCreateSuspendedSem: TSemaphore;

FInitialSuspendDone: Boolean;

{$ENDIF}

FCreateSuspended: Boolean;

FTerminated: Boolean;FSuspended: Boolean; FFreeOnTerminate: Boolean; FFinished: Boolean; FReturnValue: Integer; FOnTerminate: TNotifyEvent; FMethod: TThreadMethod; FSynchronizeException: TObject; FFatalException: TObject;

procedure CheckThreadError(ErrCode: Integer); overload; procedure CheckThreadError(Success: Boolean); overload; procedure CallOnTerminate;

{$IFDEF MSWINDOWS}

function GetPriority: TThreadPriority;

procedure SetPriority(Value: TThreadPriority);

procedure SetSuspended(Value: Boolean);

{$ENDIF}

{$IFDEF LINUX}

// ** В Linux значение приоритета (Priority) имеет тип Integer

function GetPriority: Integer;

procedure SetPriority(Value: Integer);

function GetPolicy: Integer;

procedure SetPolicy(Value: Integer);

procedure SetSuspended(Value: Boolean);

{$ENDIF}

protected

procedure DoTerminate; virtual;

procedure Execute; virtual; abstract;

procedure Synchronize(Method: TThreadMethod);

property ReturnValue: Integer read FReturnValue

write FReturnValue;

property Terminated: Boolean read FTerminated;

public

constructor Create(CreateSuspended: Boolean);

destructor Destroy; override;

procedure AfterConstruction; override;

procedure Resume;

procedure Suspend;

procedure Terminate;

function WaitFor: LongWord;

property FatalException: TObject read FFatalException;

property FreeOnTerminate: Boolean read FFreeOnTerminate

write FFreeOnTerminate;

property Handle: THandle read FHandle;

{$IFDEF MSWINDOWS}

property Priority: TThreadPriority read GetPriority

write SetPriority;

{$ENDIF}

{$IFDEF LINUX}

// ** Priority – тип Integer **

property Priority: Integer read GetPriority write SetPriority;

property Policy: Integer read GetPolicy write SetPolicy;

{$ENDIF}

property Suspended: Boolean read FSuspended writeSetSuspended;{$IFDEF MSWINDOWS}

property ThreadID: THandle read FThreadID;

{$ENDIF}

{$IFDEF LINUX}

// ** ThreadId – тип Cardinal **

property ThreadID: Cardinal read FThreadID;

{$ENDIF}

property OnTerminate: TNotifyEvent read FOnTerminate

write FOnTerminate;

end;

Как можно увидеть из объявления, класс TThread — прямой потомок (производный) от класса  TObject, следовательно, он не является компонентом. Сравнив блоки  кода IFDEF, нетрудно заметить, что  класс TThread разработан так,  чтобы  различия между Delphi  и Kylix были  минимальными, хотя  полностью их  избежать все  же  не  удалось. Кроме  того,  метод  TThread.Execute() объявлен абстрактным (abstract). Это озна чает, что класс TThread сам является абстрактным. Таким образом, можно  создавать эк земпляры классов,  производных от TThread, а экземпляр самого  класса  TThread соз дать  нельзя. Проще всего  создать  потомок класса  TThread с помощью пиктограммы Thread Object в диалоговом окне New Items (рис. 5.1), которое можно открыть, выбрав в меню File пункт New.

Рис. 5.1. Пиктограмма Thread Object

диалогового окна New Items

После  выбора элемента Thread Object в диалоговом окне New Items откроется диа логовое окно New Thread Object, в котором будет предложено ввести  имя нового объ екта. Например, можно ввести  имя TTestThread. Затем  Delphi  создаст новый модуль, содержащий этот объект. Вот как будет выглядеть его объявление:

type

TTestThread = class(TThread)

private

{ Закрытые объявления }

protected

procedure Execute; override;

end;

Как видно из примера, для создания функционального потомка класса TThread не

обходимо  переопределить  единственный метод —  Execute(). Предположим теперь,что внутри  класса TTestThread требуется выполнить сложные вычисления. В таком случае метод Execute() можно было бы определить следующим образом:

procedure TTestThread.Execute;

var

i, Answer: integer;

begin

Answer := 0;

for i := 1 to 2000000 do

inc(Answer, Round(Abs(Sin(Sqrt(i)))));

end;

Единственная цель этих вычислений — затратить максимум времени на их выпол

нение.

Теперь можно  выполнить данный пример потока, вызвав  конструктор Create(). В данном  случае для этого  достаточно щелкнуть  на кнопке главной формы, как пока зано  в следующем  фрагменте программы (во  избежание ошибок  компилятора не за будьте  включить модуль,  содержащий объект TTestThread, в раздел  uses модуля TForm1):

procedure TForm1.Button1Click(Sender: TObject);

var

NewThread: TTestThread;

begin

NewThread := TTestThread.Create(False);

end;

Если запустить приложение и щелкнуть  на вышеуказанной кнопке, то можно  убе диться, что по прежнему существует возможность работать с формой, т.е. перемещать ее или изменять ее размеры, на фоне  выполнения упомянутых вычислений.

НА ЗАМЕТКУ

Единственный логический параметр, который передается в конструктор Create() класса TThread, называется CreateSuspended. Он определяет, следует ли начинать работу потока с перевода его в приостановленное состояние. Если этот параметр ра- вен False, то метод Execute() создаваемого объекта будет вызван автоматически и без промедления. Если данный параметр равен True, то для действительного запуска потока в определенной точке приложения потребуется вызвать его метод Resume(). Это приведет к выполнению метода Execute() потока и активизирует его. Обычно па- раметр CreateSuspended устанавливается равным True только в том случае, если перед запуском потока требуется установить дополнительные свойства его объекта. Установка свойств объекта после запуска потока может привести к возникновению проблем.

При более внимательном изучении работы конструктора Create() оказывается, что он вызывает функцию библиотеки RTL Delphi BeginThread(), которая, в свою оче- редь, вызывает функцию интерфейса API Win32 CreateThread() для создания ново- го потока. Значение параметра CreateSuspended показывает, нужно ли передавать функции CreateThread() флаг CREATE_SUSPENDED.

Экземпляры потока

2

 

Теперь вернемся к методу Execute() класса  TTestThread. Обратите внимание: он содержит локальную  переменную i. Давайте рассмотрим, что  произойдет с пере менной i, если  создать  два экземпляра класса  TTestThread. Может  ли значение та кой переменной одного  потока перезаписать значением одноименной переменной другого? Имеет ли первый поток  преимущество перед  вторым? Происходит ли в этом случае аварийное завершение потока?  Ответы на все три вопроса одинаковы: нет, нет и нет.  Система  Win32  поддерживает для каждого  работающего в системе потока от дельный стек. Это означает, что при создании нескольких экземпляров класса TTest- Thread каждый  из них будет иметь  собственную копию  переменной i в своем  собст венном стеке.  Следовательно, в этом  отношении все потоки будут действовать неза висимо друг от друга .

Однако необходимо заметить, что понятие “одной и той же” переменной (которая в своем потоке действует  независимо от “коллег”)  отнюдь  не переносится на глобаль ные переменные. Более  подробная информация по этой  теме приведена в настоящей главе далее.

Завершение потока

Поток TThread считается завершенным, когда завершается выполнение его мето да Execute(). В этот  момент  вызывается стандартная процедура Delphi End- Thread(), которая, в свою  очередь, вызывает функцию  API Win32  ExitThread(). Данная функция должным образом освобождает стек потока и сам объект потока API. По завершении ее работы поток  перестает существовать и все использованные им ре сурсы будут освобождены.

По  окончании  использования объекта TThread нужно  гарантированно уничто жить и соответствующий объект Object  Pascal. Только в таком  случае можно  быть уве ренным в  корректном  освобождении всей  памяти, занимаемой данным   объектом. И хотя это происходит автоматически после завершения процесса, возможно придет ся заняться освобождением объекта несколько раньше, чтобы  исключить возникно вение  утечки памяти в приложении. Простейший способ  гарантированно освободить объект TThread состоит в установке его  свойства FreeOnTerminate равным значе нию True. Причем это можно  сделать  в любое время, до завершения выполнения ме тода Execute(). Например, для объекта TTestThread такое  свойство устанавливает ся внутри метода Execute():

procedure TTestThread.Execute;

var

i: integer;

begin

FreeOnTerminate := True;

for i := 1 to 2000000 do

inc(Answer, Round(Abs(Sin(Sqrt(i)))));

end;

2 Как, впрочем, и экземпляры любых других классов. — Прим. ред. поддерживает также  событие OnTerminate, которое происходит при  завершении работы потока. Допускается освобождения объекта TThread внутри обработчика этого события.

НА ЗАМЕТКУ

Событие OnTerminate объекта TThread вызывается в контексте основного потока приложения. Это означает, что внутри обработчика данного события доступ к свойст- вам и методам VCL разрешается выполнять свободно, не прибегая к услугам метода Synchronize(), речь о котором пойдет в следующем разделе.

Следует иметь  в виду, что метод  Execute() объекта потока самолично несет  ответ ственность за проверку состояния свойства Terminated с целью  определения необхо димости в досрочном завершении. Хотя это означает еще одно усложнение, о котором следует помнить при работе с потоками, достоинством этой архитектуры класса являет ся полная гарантия того, что никакая “нечистая сила” не выдернет коврик из под ваших ног в самый  неподходящий момент. А значит, всегда  будет существовать возможность выполнить все необходимые операции очистки по окончании работы потока. Подоб ную проверку состояния свойства Terminated довольно легко  добавить в метод  Exe- cute() объекта TTestThread — она будет выглядеть следующим образом:

procedure TTestThread.Execute;

var

i: integer;

begin

FreeOnTerminate := True;

for i := 1 to 2000000 do begin

if Terminated then Break;

inc(Answer, Round(Abs(Sin(Sqrt(i)))));

end;

end;

CОВЕТ

В случае аварийной ситуации для завершения выполнения потока можно также ис- пользовать функцию API Win32 TerminateThread(). К этой функции следует обра- щаться только в том случае, если другие варианты отсутствуют — например, когда по- ток попадает в бесконечный цикл и перестает реагировать на сообщения. Эта функция определяется следующим образом:

function TerminateThread(hThread:THandle;dwExitCode:DWORD);

Свойство THandle объекта TThread содержит дескриптор потока, присвоенный ему функциями API, поэтому к данной функции можно обращаться и с помощью следующе- го синтаксиса:

TerminateThread(MyHosedThread.Handle, 0);

При использовании этой функции следует помнить о крайне неприятных побочных эффек- тах, которые она способна вызывать. Во-первых, она может вести себя по-разному в опе- рационных системах Windows NT/2000 и Windows 95/98. Под управлением Windows 95/98 функция TerminateThread() освобождает стек, связанный с потоком, а в Windows NT стек не освобождается до тех пор, пока не завершится процесс. Во-вторых, во всех опера-ционных системах Win32 функция TerminateThread(), невзирая ни на что, просто оста-

навливает выполнение в случайном месте и не позволяет

блоку try..finally освободить ресурсы. Это означает, что файлы, открытые потоком, могут остаться незакрытыми, память, выделенная потоком, может не быть освобождена и т.д. Кроме того, динамически компонуемые библиотеки (DLL), загруженные данным процес- сом, не получают соответствующего уведомления при завершении потока с помощью функции TerminateThread(), что может вызвать проблемы при закрытии DLL. Более подробная информация о возможности уведомления DLL о работе потоков приведена в главе 6 “Динамически компонуемые библиотеки”.

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии