На этом шаге мы рассмотрим особенности использования приоритетов потоков.
В классе TThread также определено свойство Priority, которое может принимать следующие значения: tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest, tpTimeCritical. Это свойство определяет долю времени, которую процессор отводит для выполнения фонового потока но сравнению с главным потоком. При значении свойства Priority = tpNormal эти времена совпадают. Например, если 10 секунд времени было потрачено на решение задачи, то 5 секунд нроцессор выделил главному потоку и 5 секунд фоновому. Поток с приоритетом tpIdle практически не мешает выполнению главного потока (хотя процессор все же выделяет время на его выполнение). Поток с таким приоритетом можно использовать, например, для проверки данных в буфере обмена и в зависимости от их изменений делать доступными или недоступными кнопки. Другая крайность - приоритет tpHighest - время для работы главного потока практически не остается. Если в потоке с таким приоритетом выводить текущее значение счетчика на экран, то оно отображаться не будет - до того как графика успеет отобразиться на экране, фоновый поток прервет главный поток, чтобы обновить значение счетчика.
Несколько слов о потоках с приоритетом tpTimeCritical. Это наивысший приоритет. Создание приложений, использующих потоки с таким нриоритетом, требует соблюдения определенных правил. Дело в том, что некоторые методы ядра Windows являются асинхронными, но выполняются с очень большим нриоритетом. Приоритет tpTimeCritical сравним с приоритетом вызова этих методов. Это означает, что при вызове такого метода его выполнение может не успеть завершиться до выполнения следующего оператора из созданного приложения. Имеется список функций ядра, которые нельзя использовать в приложениях с приоритетом tpTimeCritical. В традиционных приложениях такой приоритет не нужен, но он может потребоваться в некоторых играх при показе видеоизображений.
Завершая разговор о приоритетах, следует предостеречь от попыток с их помощью синхронизировать время завершения работы потоков. Предположим, имеется два фоновых потока, причем при одинаковом приоритете они имеют сравнимое время завершения. Предположим, что для завершения работы одного из этих потоков требуются результаты расчетов, выполненные в другом потоке; то есть второй поток должен завершиться раньше первого. Казалось бы, первому потоку можно назначить приоритет tpLowest, а второму - tpHighest, и задача будет решена. Это абсолютно верные рассуждения, но только при условии проведения вычислений на однопроцессорных компьютерах. При переходе же на многопроцессорные системы эти две задачи будут выполняться разными процессорами, и приоритет перестанет играть роль - каждый поток займет максимально возможное время процессора. Для синхронизации процессов необходимо использовать сигнальные объекты - о них будет сказано далее.
Следует отметить свойство FreeOnTerminate класса TThread. Если его значение равно True, то после завершения метода Execute автоматически вызывается деструктор. При значении же этого свойства, равном False, деструктор требуется вызывать в явном виде из кода приложения. Из других методов следует отметить метод Terminate. Вызов этого метода присваивает значение True свойству Terminated (только для чтения). Он не прерывает код, описанный в методе Execute. Однако при написании метода Execute программист обязан достаточно часто проверять значение свойства Terminated, и если вдруг оно станет равным True, максимально быстро прекратить выполнение метода Execute. Никаких сообщений пользователю из потока не выводится (это надо делать раньше, до вызова метода Terminate), достаточно только освободить системные ресурсы (если они были зарезервированы) и прекратить выполнение кода.
Класс TThread имеет единственный обработчик событий OnTerminate, который вызывается при завершении метода Execute. В этом обработчике событий можно, например, узнать результаты расчетов, выполненных в потоке. Код при стандартном способе использования этого обработчика событий выглядит следующим образом:
procedure TForm1.ButtonlClick(Sender: TObject); begin with TMyThread.Create(True) do begin FreeOnTerminate:=True; OnTerminate:=ThreadDone; Resume; end: end: procedure TForm1.ThreadDone(Sender: TObject); begin with Sender as TMyThread do begin // Здесь реализуется код для обработки результатов расчетов end; end;
После завершения кода в потоке будет вызван метод ThreadDone, где можно прочитать результаты расчетов. Забегая немного вперед, отметим, что для чтения переменных класса ТМуThread в методе OnTerminate не требуется синхронизации.
И, наконец, из фоновых потоков не должны возбуждаться исключения. Приложение не может корректно обработать исключения, если они возбуждены вне главного потока. Поэтому весь код в методе Execute следует помещать в блок перехвата исключения:
procedure TMyThread.Execute; begin try // Выполняемый код здесь except // Запрещается использовать директиву raise в этом месте! // Возможные действия - возврат кода ошибки end; end;
Ситуация с исключениями осложняется тем, что они корректно обрабатываются отладчиком Delphi и поэтому в режиме отладки не воспроизводятся.
На следующем шаге мы рассмотрим понятие о синхронизации.