На этом шаге мы рассмотрим особенности использования этого класса.
В 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 имеет два метода:
Конструктор класса 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, который начинает выполнение кода. Это единственно правильный способ инициализации данных: поток не запускается до тех пор, пока не заданы значения всех переменных.
На следующем шаге мы закончим изучение этого вопроса.