Потоки в Delphi. TThread

Уроки для начинающих   20 декабря 2012  Автор статьи: admin 
geekbrains.ru/

В этом уроке мы рассмотрим возможность разработки многопоточных приложений в Delphi. Для начала давайте разберем, для чего же нам нужны эти самые потоки, что они из себя представляют.

Принцип работы

Для того, чтобы реализовать многозадачность, операционная система выделяет каждому приложению определенное количество процессорного времени, которое она старается распределить по заданным приоритетам. Таким образом производительность каждой такой задачи/потока ограничивается операционной системой в рамках своего процессорного времени. Для того, чтобы выполнять какие-либо действия параллельно (псевдопараллельно), мы можем создавать дополнительные потоки, и в них выполнять те самые действия. Если грамотно распределить какие-либо сложные операции в нашем приложении, то это поможет значительно увеличить производительность ПО.

Реализация. Класс TThread

В модуле Classes в Delphi существует специальный класс TThread, предназначенный для создания потоков. Не забудьте подключить модуль Classes. Рассмотрим создание простого потока. Для этого нам потребуется написать свой класс, который будет наследоваться от класса TThread. Делать мы это будем в разделе type нашего модуля.
[cc lang=»delphi»]type
TMyThread = class(TThread) // Описываем класс нашего нового потока
private
{ Private declarations }
protected
procedure Execute; override; // В методе Execute будет находиться непосредственно тело самого потока —
// код, который будет в нем обрабатываться.
end;[/cc]
Теперь установим курсор на наше описание метода Execute, нажмем Ctrl+Shift+C, чтобы перейти к реализации этого метода. Мы увидим следующий код:
[cc lang=»delphi»]procedure TMyThread.Execute;
begin

end;[/cc]
В теле этой процедуры мы и должны будем написать код, который будет обрабатываться в нашем потоке.
Вот таким несложным способом мы можем реализовать наш поток. Осталось только разобраться с тем, как его можно использовать. Создадим экземпляр нашего потока TMyThread:
[cc lang=»delphi»]var MyFirstThread: TMyThread;
begin
MyFirstThread := TMyThread.Create(False);
end;[/cc]
В нашем случае, мы передали конструктору Create нашего потока параметр false. Это означает, что наш поток будет запущен сразу после создания. Если передать True, то поток будет запущен тогда, когда мы вызовем у него метод Resume. Сразу после запуска, поток начнет выполнять программный код метода Execute.
Потокам можно устанавливать их приоритет, от которого будет зависеть количество процессорного времени, которое выделит ОС на данный поток. Другими словами, мы можем настраивать производительность этого потока относительно других. Делается это при помощи свойства Priority у нашего потока:
[cc lang=»delphi»]MyFirstThread.Priority := tpNormal;[/cc]
Существует 7 уровней приоритета для потоков:

  • tpIdle — Самый низкий уровень, поток выполнятся во время «простоя» системы.
  • tpLowest
  • tpLower
  • tpNormal
  • tpHigher
  • tpHighest
  • tpTimeCritical — Самый высокий уровень. Приоритет исполняемого потока равноценен приоритету ядра ОС.

Не переусердствуйте с приоритетами 🙂 Использование tpTimeCritical или tpHighest у нескольких потоков может заставить ваш компьютер «задуматься». Старайтесь использовать tpLower или tpNormal.
Также существует полезное свойство FreeOnTerminate, если установить ему значение True, то поток будет автоматически уничтожен, как только будет завершен программный код метода Execute.
Для выполнения каких-либо циклических операций внутри потока, удобно использовать следующую конструкцию:
[cc lang=»delphi»]procedure TMyThread.Execute;
begin
while true do
begin
{Do something}
end;
end;[/cc]
Содержимое цикла While потока будет обрабатываться, пока внутри цикла не будет вызван break, или пока поток не будет завершен вручную.
Ну, и наконец, уничтожить поток можно методом Terminate:
[cc lang=»delphi»]MyFirstThread.Terminate;[/cc]
Соответственно существует метод Terminated, который возвращает true, если поток был завершен/уничтожен.
Бывает полезно использовать метод Suspend, для того, чтобы временно приостановить выполнение потока, после чего возобновить работу потока можно будет при помощи метода Resume. Чтобы узнать, приостановлен ли поток, можно использовать метод Suspended, который возвращает true, если поток приостановлен.

Синхронизация

Одной из самых главных проблем в работе с потоками является невозможность обращаться к каким-либо данным из двух потоков одновременно.
[warning]Вероятнее всего такое одновременное обращение приведет к ошибке Access Violation.[/warning]
Самым типичным примером возникновения такой ошибки является обращение к GUI (к визуальным компонентам формы) из нового потока. Дело заключается в том, что GUI отрисовывается и постоянно обрабатывается в главном потоке вашего приложения, в котором также обрабатывается и другой ваш основной код. Так вот если к GUI мы обратимся еще из какого-то потока, то возникнет ошибка, т.к. GUI уже занят другим потоком. Одним из самых распространенных решений этой проблемы является синхронизация, о которой мы сейчас и поговорим.
Синхронизация позволяет выполнить какой-либо метод в главном потоке приложения, вызвав его при этом в другом потоке.
[warning]Запомните! Все методы, которые вы будете вызывать из тела потока, будут вызываться и обрабатываться в этом потоке. И только для вызова метода в главном потоке, используется синхронизация.[/warning]
Для реализации примера синхронизации создадим метод ChangeGUI, в котором мы будем обращаться к нашему GUI, и соответственно, который будет выполняться в главном потоке, а вызываться из нашего нового. Также добавим в наш поток поле с названием num, в котором мы будем хранить число и постоянно увеличивать его внутри тела потока (для примера).
[cc lang=»delphi»]type
TMyThread = class(TThread) // Описываем класс нашего нового потока
private
{ Private declarations }
num: integer;
protected
procedure ChangeGUI; // Метод, в котором мы будем обращаться к GUI, и который будет обрабатываться в главном потоке.
procedure Execute; override; // В методе Execute будет находиться непосредственно тело самого потока —
// код, который будет в нем обрабатываться.
end;

procedure TMyThread.ChangeGUI;
begin
Form2.Label1.Caption := IntToStr(num); // Выводим новое значение num
end;

procedure TMyThread.Execute;
begin
while True do
begin
if num = 1000000 then
num := 0;

Inc(num); // Увеличиваем num на единицу

Synchronize(ChangeGUI); // Та самая синхронизация! Изменяем надпись Label1 в главном потоке
end;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
MyFirstThread: TMyThread;
begin
MyFirstThread := TMyThread.Create(False); // Создаем наш поток
MyFirstThread.Priority := tpLower; // Устанавливаем приоритет
end;
[/cc]
Как вы уже заметили из приведенного выше листинга, для того, чтобы вызвать какой-либо метод потока в главном потоке, необходимо передать этот метод в качестве параметра для метода Synchronize. Если вызывать метод не через Synchronize, то я уверен, что ваша программа долго не проживет 🙂 Скорее всего возникнет какой-то конфликт либо в главном потоке, либо в нашем новом, в результате чего программа завершит свое выполнение с ошибкой.
В общем, изучайте потоки, экспериментируйте с ними, а я как всегда прилагаю исходник, который вы можете скачать по этой ссылке.

Научиться программировать

  • на Delphi

  • на Java

  • на C++

geekbrains.ru/
geekbrains.ru/