Навигация
Категории
C++,C#,C [19]
Уроки по средам разработки приложений с использованием C,C++,C#, а также .Net, DirectX, OpenGL и других
Delphi [14]
Уроки работы в среде программирования Delphi
Basic [13]
Уроки разработки приложений в среде Basic
DirectX [8]
Уроки по работе с DirectX, включая Direct3D и другие инструменты
Web [7]
Уроки по "Веб-программированию"
JavaScript [12]
Уроки по кодингу в Java и JavaScript
XNA [9]
Статьи и уроки программирование в среде XNA C++&C#
Профиль
Статистика
Rambler's Top100

Онлайн всего: 2
Гостей: 2
Пользователей: 0
Locations of visitors to this page
Главная » Статьи » Программирование » Delphi

Работа с потоками в Delphi
Данная статья предназначена для начинающих программистов, которые никогда не работали с потоками, и хотели бы узнать основы работы с ними. Желательно, чтоб читатель знал основы ООП и имел какой-нибудь опыт работы в Delphi. Для начала давайте определимся, что под словом "поток" я подразумеваю именно Thread, который еще имеет название "нить". Нередко встречал на форумах мнения, что потоки не нужны вообще, любую программу можно написать так, что она будет замечательно работать и без них. Конечно, если не делать ничего серьёзней "Hello World" это так и есть, но если постепенно набирать опыт, рано или поздно любой начинающий программист упрётся в возможности "плоского" кода, возникнет необходимость распараллелить задачи. А некоторые задачи вообще нельзя реализовать без использования потоков, например работа с сокетами, COM-портом, длительное ожидание каких-либо событий, и т.д.

Всем известно, что Windows система многозадачная. Попросту говоря, это означает, что несколько программ могут работать одновременно под управлением ОС. Все мы открывали диспетчер задач и видели список процессов. Процесс - это экземпляр выполняемого приложения. На самом деле сам по себе он ничего не выполняет, он создаётся при запуске приложения, содержит в себе служебную информацию, через которую система с ним работает, так же ему выделяется необходимая память под код и данные. Для того, чтобы программа заработала, в нём создаётся поток. Любой процесс содержит в себе хотя бы один поток, и именно он отвечает за выполнение кода и получает на это процессорное время. Этим и достигается мнимая параллельность работы программ, или, как её еще называют, псевдопараллельность. Почему мнимая? Да потому, что реально процессор в каждый момент времени может выполнять только один участок кода. Windows раздаёт процессорное время всем потокам в системе по очереди, тем самым создаётся впечатление, что они работают одновременно. Реально работающие параллельно потоки могут быть только на машинах с двумя и более процессорами.

Для создания дополнительных потоков в Delphi существует базовый класс TThread, от него мы и будем наследоваться при реализации своих потоков. Для того, чтобы создать "скелет" нового класса, можно выбрать в меню File - New - Thread Object, Delphi создаст новый модуль с заготовкой этого класса. Я же для наглядности опишу его в модуле формы. Как видите, в этой заготовке добавлен один метод - Execute. Именно его нам и нужно переопределить, код внутри него и будет работать в отдельном потоке. И так, попробуем написать пример - запустим в потоке бесконечный цикл:
TNewThread = class(TThread)
  private
    { Private declarations }
  protected
    procedure Execute; override;
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TNewThread }

procedure TNewThread.Execute;
begin
  while true do {ничего не делаем};
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  NewThread: TNewThread;
begin
  NewThread:=TNewThread.Create(true);
  NewThread.FreeOnTerminate:=true;
  NewThread.Priority:=tpLower;
  NewThread.Resume;
end;
Запустите пример на выполнение и нажмите кнопку. Вроде ничего не происходит - форма не зависла, реагирует на перемещения. На самом деле это не так - откройте диспетчер задач и вы увидите, что процессор загружен по-полной. Сейчас в процессе вашего приложения работает два потока - один был создан изначально, при запуске приложения. Второй, который так грузит процессор - мы создали по нажатию кнопки. Итак, давайте разберём, что же означает код в Button1Click:
NewThread:=TNewThread.Create(true); 
тут мы создали экземпляр класса TNewThread. Конструктор Create имеет всего один параметр - CreateSuspended типа boolean, который указывает, запустить новый поток сразу после создания (если false), или дождаться команды (если true).
New.FreeOnTerminate := true; 
свойство FreeOnTerminate определяет, что поток после выполнения автоматически завершится, объект будет уничтожен, и нам не придётся его уничтожать вручную. В нашем примере это не имеет значения, так как сам по себе он никогда не завершится, но понадобится в следующих примерах.
NewThread.Priority:=tpLower; 
Свойство Priority, если вы еще не догадались из названия, устанавливает приоритет потока. Да да, каждый поток в системе имеет свой приоритет. Если процессорного времени не хватает, система начинает распределять его согласно приоритетам потоков. Свойство Priority может принимать следующие значения:
  • tpTimeCritical - критический
  • tpHighest - очень высокий
  • tpHigher - высокий
  • tpNormal - средний
  • tpLower - низкий
  • tpLowest - очень низкий
  • tpIdle - поток работает во время простоя системы
Ставить высокие приоритеты потокам не стоит, если этого не требует задача, так как это сильно нагружает систему.
NewThread.Resume; 
Ну и собственно, запуск потока.

Думаю, теперь вам понятно, как создаются потоки. Заметьте, ничего сложного. Но не всё так просто. Казалось бы - пишем любой код внутри метода Execute и всё, а нет, потоки имеют одно неприятное свойство - они ничего не знают друг о друге. И что такого? - спросите вы. А вот что: допустим, вы пытаетесь из другого потока изменить свойство какого-нибудь компонента на форме. Как известно, VCL однопоточна, весь код внутри приложения выполняется последовательно. Допустим, в процессе работы изменились какие-то данные внутри классов VCL, система отбирает время у основного потока, передаёт по кругу остальным потокам и возвращает обратно, при этом выполнение кода продолжается с того места, где приостановилось. Если мы из своего потока что-то меняем, к примеру, на форме, задействуется много механизмов внутри VCL (напомню, выполнение основного потока пока "приостановлено"), соответственно за это время успеют измениться какие-либо данные. И тут вдруг время снова отдаётся основному потоку, он спокойно продолжает своё выполнение, но данные уже изменены! К чему это может привести - предугадать нельзя. Вы можете проверить это тысячу раз, и ничего не произойдёт, а на тысяча первый программа рухнет. И это относится не только к взаимодействию дополнительных потоков с главным, но и к взаимодействию потоков между собой. Писать такие ненадёжные программы конечно нельзя.

Синхронизации потоков

Если вы создали шаблон класса автоматически, то, наверное, заметили комментарий, который дружелюбная Delphi поместила в новый модуль. Он гласит: "Methods and properties of objects in visual components can only be used in a method called using Synchronize". Это значит, что обращение к визуальным компонентам возможно только путём вызова процедуры Synchronize. Давайте рассмотрим пример, но теперь наш поток не будет разогревать процессор впустую, а будет делать что-нибудь полезное, к примеру, прокручивать ProgressBar на форме. В качестве параметра в процедуру Synchronize передаётся метод нашего потока, но сам он передаётся без параметров. Параметры можно передать, добавив поля нужного типа в описание нашего класса. У нас будет одно поле - тот самый прогресс:
TNewThread = class(TThread)
  private
    Progress: integer;
    procedure SetProgress;
  protected
    procedure Execute; override;
  end;
...

procedure TNewThread.Execute;
var
  i: integer;
begin
  for i:=0 to 100 do
  begin
    sleep(50);
    Progress:=i;
    Synchronize(SetProgress);
  end;
end;

procedure TNewThread.SetProgress;
begin
  Form1.ProgressBar1.Position:=Progress;
end;
Вот теперь ProgressBar двигается, и это вполне безопасно. А безопасно вот почему: процедура Synchronize на время приостанавливает выполнение нашего потока, и передаёт управление главному потоку, т.е. SetProgress выполняется в главном потоке. Это нужно запомнить, потому что некоторые допускают ошибки, выполняя внутри Synchronize длительную работу, при этом, что очевидно, форма зависает на длительное время. Поэтому используйте Synchronize для вывода информации - то самое двигание прогресса, обновления заголовков компонентов и т.д.

Вы наверное заметили, что внутри цикла мы используем процедуру Sleep. В однопоточном приложении Sleep используется редко, а вот в потоках его использовать очень удобно. Пример - бесконечный цикл, пока не выполнится какое-нибудь условие. Если не вставить туда Sleep мы будем просто нагружать систему бесполезной работой.

Надеюсь, вы поняли как работает Synchronize. Но есть еще один довольно удобный способ передать информацию форме - посылка сообщения. Давайте рассмотрим и его. Для этого объявим константу:
const
  PROGRESS_POS = WM_USER+1;
В объявление класса формы добавим новый метод, а затем и его реализацию:
  TForm1 = class(TForm)
    Button1: TButton;
    ProgressBar1: TProgressBar;
    procedure Button1Click(Sender: TObject);
  private
    procedure SetProgressPos(var Msg: TMessage); message PROGRESS_POS;
  public
    { Public declarations }
  end;
...

procedure TForm1.SetProgressPos(var Msg: TMessage);
begin
  ProgressBar1.Position:=Msg.LParam;
end;
Теперь мы немного изменим, можно сказать даже упростим, реализацию метода Execute нашего потока:
procedure TNewThread.Execute;
var
  i: integer;
begin
  for i:=0 to 100 do
  begin
    sleep(50);
    SendMessage(Form1.Handle,PROGRESS_POS,0,i);
  end;
end;
Используя функцию SendMessage, мы посылаем окну приложения сообщение, один из параметров которого содержит нужный нам прогресс. Сообщение становится в очередь, и согласно этой очереди будет обработано главным потоком, где и выполнится метод SetProgressPos. Но тут есть один нюанс: SendMessage, как и в случае с Synchronize, приостановит выполнение нашего потока, пока основной поток не обработает сообщение. Если использовать PostMessage этого не произойдёт, наш поток отправит сообщение и продолжит свою работу, а уж когда оно там обработается - неважно. Какую из этих функций использовать - решать вам, всё зависит от задачи.

Вот, в принципе, мы и рассмотрели основные способы работы с компонентами VCL из потоков. А как быть, если в нашей программе не один новый поток, а несколько? И нужно организовать работу с одними и теми же данными? Тут нам на помощь приходят другие способы синхронизации. Один из них мы и рассмотрим. Для его реализации нужно добавить в проект модуль SyncObjs.

Критические секции

Работают они следующим образом: внутри критической секции может работать только один поток, другие ждут его завершения. Чтобы лучше понять, везде приводят сравнение с узкой трубой: представьте, с одной стороны "толпятся" потоки, но в трубу может "пролезть" только один, а когда он "пролезет" - начнёт движение второй, и так по порядку. Еще проще понять это на примере и тем же ProgressBar'ом. Итак, запустите один из примеров, приведённых ранее. Нажмите на кнопку, подождите несколько секунд, а затем нажмите еще раз. Что происходит? ProgressBar начал прыгать. Прыгает потому, что у нас работает не один поток, а два, и каждый из них передаёт разные значения прогресса. Теперь немного переделаем код, в событии onCreate формы создадим критическую секцию:
var
  Form1: TForm1;
  CriticalSection: TCriticalSection;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
  CriticalSection:=TCriticalSection.Create;
end;
У TCriticalSection есть два нужных нам метода, Enter и Leave, соответственно вход и выход из неё. Поместим наш код в критическую секцию:
procedure TNewThread.Execute;
var
  i: integer;
begin
  CriticalSection.Enter;
  for i:=0 to 100 do
  begin
    sleep(50);
    SendMessage(Form1.Handle,PROGRESS_POS,0,i);
  end;
  CriticalSection.Leave;
end;
Попробуйте запустить приложение и нажать несколько раз на кнопку, а потом посчитайте, сколько раз пройдёт прогресс. Понятно, в чем суть? Первый раз, нажимая на кнопку, мы создаём поток, он занимает критическую секцию и начинает работу. Нажимаем второй - создаётся второй поток, но критическая секция занята, и он ждёт, пока её не освободит первый. Третий, четвёртый - все пройдут только по-очереди.

Критические секции удобно использовать при обработке одних и тех же данных (списков, массивов) разными потоками. Поняв, как они работают, вы всегда найдёте им применение.

В этой небольшой статье рассмотрены не все способы синхронизации, есть еще события (TEvent), а так же объекты системы, такие как мьютексы (Mutex), семафоры (Semaphore), но они больше подходят для взаимодействия между приложениями. Остальное, что касается использования класса TThread, вы можете узнать самостоятельно, в help'е всё довольно подробно описано. Цель этой статьи - показать начинающим, что не всё так сложно и страшно, главное разобраться, что есть что. И побольше практики - самое главное опыт!

Источник: http://www.cyberguru.ru/programming/delphi/threads.html
Категория: Delphi | Добавил: gforcer (19.06.2010) | Автор: Админ
Просмотров: 6228 | Комментарии: 4 | Теги: Урок, Поток, нить, создание, Stream, линк, программирование, работа, статья, Delphi | Рейтинг: 4.0/1
Всего комментариев: 4
4 решения судов общей юрисдикции  
0
http://ukrsud.info Наш вебсайт - информационный ресурс, призванный помочь каждому человеку в получении надежных сведений по проблемам организации защиты своих законных интересов.
http://ukrsud.info/reshenie-suda-po-isku-fizicheskogo-lica-263 - основного долга с учетом
Мы осуществили обобщения судебной практики Украины.
http://ukrsud.info/reshenie-suda-po-isku-gosudarstvennogo-27 - 22 Хозяйственного процессуального кодекса Украины
Судебная практика включает организацию, процесс и итоги деятельности судебной системы по притворению в жизнь правосудия, но первее всего объектом ее исследования считается правоприменительная работа судов, анализ качества судебной работы.
http://ukrsud.info/reshenie-suda-po-isku-o-priznanii-nedejstvitelnymi - «О Государственном комитете
Благодаря применению современных поисковых технологий, у Вас появилась возможность быстро ознакомиться с существующей судебной практикой по схожим делам и произвести вывод, в чью пользу формируется современная украинская судебная практика.
http://ukrsud.info/reshenie-suda-po-isku-fonda-kommunalnogo-2 - городского совета и ООО

3 broagmadags  
0
Znakomye predostavili МНК URL: http://srub-dizain.ru/pilomaterial - obreznaya doska производителя Киржач
и я сразу организовал заказ! Мне тогда срочно нужен был брусок для лестницы. В общем после этого я и повёз туда деньги! Там реально всё качественно сделано и профилированный брус естественной влажности не тяжело выбрать. Всему данному событию сопутствовала трудная навигация ресурса, которую быстро обнаружил.Очень круто конечно то, что это изготовитель пиломатериалов – но сайтец очень трудно понять.

2 Taipsemam  
0
emra shqiptar 2011
delete plz

1 KinoUndetsnut  
0
Ищу вот сайт с фильмами, чтобы мона скачать и смотреть онлайн
Спасибо.

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск
Друзья сайта
Демотиваторы
Copyright Зямаев Денис © 2024