На этом шаге мы рассмотрим особенности использования этого класса.
В Delphi вычисления в потоках реализуются при помощи абстрактного класса TThread, для чего необходимо создать класс-потомок и при помощи директивы override перекрыть абстрактный метод Execute:
type TMyThread = class(TThread) protected procedure Execute: override; end; . . . procedure TMyThread.Execute; begin // Программный код end;
Код, который реализуется в методе Execute, выполняется в отдельном потоке. Для его запуска просто необходимо создать экземпляр класса TMyThread:
ТМуThread.Create(False);
СОМ-объекты самостоятельно, без использования класса TThread, поддерживают вычисления в потоках. Однако этот класс полезно рассмотреть с точки зрения анализа сложностей, возникающих при создании
многопоточных приложений.
При запуске любого приложения автоматически создается отдельный поток, который называют главным. В коде главного потока реализуется прием сообщений Windows, нрорисовка графики на экране,
обработка событий (если обработчики специально не созданы так, чтобы выполняться в отдельном потоке). Создание экземпляра класса-потомка TThread означает, что код в методе Execute будет выполняться
параллельно с кодом главного потока, периодически прерывая выполнение кода главного потока, - создается фоновый поток. Он может находиться в одном из двух состояний: ожидание и выполнение. Если
фоновый поток находится в состоянии ожидания, то процессор ему просто не выделяет время. При этом все время отдается другим потокам. Соответственно, класс TThread имеет два метода:
- Suspend - переводит работающий поток в состояние ожидания;
- Resume - переводит ожидающий ноток в рабочее состояние.
Конструктор класса TThread принимает в качестве параметра логическую переменную CreateSuspended. Если ее значение равно True, то конструктор полностью отрабатывается, но поток находится в
состоянии ожидания. Для его запуска требуется вызов метода Resume. При значении этого параметра, равном False, код в методе Execute начинает выполняться немедленно после отработки конструктора.
Нельзя вызывать конструктор класса-потомка TThread с параметром False, если происходит инициализация свойств в классе:
type TMyThread = class(TThread) protected procedure Execute: override; public FR: Single; end; . . . . procedure TMyThread.Execute; var R: Single; begin . . . . R := Sqrt(FR - 1); . . . . end; . . . . procedure TForm1.ButtonlClick(Sender:TObject); begin with TMyThread.Create(False) do FR := 5; end;
На первый взгляд этот код не содержит ошибки: создается отдельный ноток, присваивается начальное значение переменной FR и продолжается расчет с использованием этого значения. Однако поскольку после отработки
конструктора код, реализованный в методе Execute, выполняется в отдельном потоке, то оператор R := sqrt(FR-1) из метода ТМуThread.Execute может быть выполнен раньше оператора FR := 5 из
метода TForm1.ButtonlClick. Соответственно, значение переменной FR после отработки конструктора равно 0, и в такой ситуации будет происходить исключение ЕInvalidOp (попытка извлечь квадратный
корень из -1). Поиск и устранение такого типа ошибки усложняются тем, что, вообще говоря, эта ошибка непостоянна и в некоторых случаях может проявляться очень редко. Для устранения указанной ошибки код в методе
Button1Click следует переписать:
procedure TForm1.ButtonlClick(Sender:TObject); begin with TMyThread.Create(True) do begin FR := 5; Resume; end; end;
В приведенном фрагменте кода первоначально создается экземпляр класса TMyThread в режиме ожидания, затем присваиваются начальные значения всем свойствам и переменным класса и только после этого вызывается
метод Resume, который начинает выполнение кода. Это единственно правильный способ инициализации данных: поток не запускается до тех пор, пока не заданы значения всех переменных.
На следующем шаге мы закончим изучение этого вопроса.