EndThread — Процедура Delphi


Содержание

EndThread — Процедура Delphi

Доброго дня всем. Написал простейшую программку для разбора полетов с потоками. Создаю поток:

hThread:THandle;
ThreadID:Cardinal;
.
Button1Click
hThread:=BeginThread(nil,0,@ThreadProc,nil,THREAD_QUERY_INFORMATION,ThreadID);

Button2Click
вот так убиваю поток:
var
ExitCode:Cardinal;
GetExitCodeThread(hThread,ExitCode);
EndThread(ExitCode);

Программка закрываеться. В чем тут дело.

В том что EndThread() выполняется не в контексте потока, о котором ты думаешь и хэндл которого лежит в hThread.

а чего ты удивляешься, справку то читал про EndThread?

так, с конфиренцией согласен..=)
>Сергей М. © (27.02.08 14:58) [1]
да, вы правы.. Наверное нужно передавать hThread в процедуру потока
ThreadProc и там делать EndThread.

>Palladin © (27.02.08 14:58) [2]
вот что-то читал в MSDN, по поводу EndThread, там как раз шла речь о процедуре потока.


> Наверное нужно передавать hThread в процедуру потока

Ты где-нибудь средим параметров ф-ции EndThread видишь хоть что-либо отдаленно напоминающее хэндл чего-то там ?


> вот что-то читал в MSDN, по поводу EndThread, там как раз
> шла речь о процедуре потока.

в MSDN нет ничего про EndThread, не документируют MSсовцы system.pas делфовый, от гады. EndThread завершает тот поток (посредством ExitThread) в контексте которого он вызывается. чего не понятного? как ты думаешь в контексте какого потока исполняется Button1Click ?

фу, млин, Button2Click тоесть.

>Palladin © (27.02.08 15:19) [6]
выполняется в главном потоке, тоесть в потоке программы, тоесть
не в ThreadProc.

>Сергей М. © (27.02.08 15:08) [4]
согласен. Дак как же убить поток? TerminateThread помогает, но весде пишется что сей метод не годится, правильнее EndThread.


> выполняется в главном потоке, тоесть в потоке программы,
> тоесть
> не в ThreadProc.

угу. значит и убивает он какой поток? хотя судя по


> согласен. Дак как же убить поток? TerminateThread помогает,
> но весде пишется что сей метод не годится, правильнее EndThread.
>

ты нифига не понял.

>Palladin © (27.02.08 15:25) [8]
угу. значит и убивает он какой поток?
должен убивать hThread — а не основной поток.
А почему тогда TerminateThread(hThread) — убивает того, кого надо.

хотя судя по
вот отдуда и непонятки TerminateThread и EndThread.

Да потому что TerminateThread принимает в параметрах кого надо убивать.

Дай ему умереть самому)

Просто скажи ему тем или иным образом «умри !» — и пусть он застрелит себя сам, как только завершит дела свои скорбные)

Вот уж когда он откажется (или глухой/слепой/немой он от рождения), вот тогда и убийство средствами TerminateThread может быть оправдано.

хочешь адекватно прерывать свой поток извне, заводи для него флаг убиения, и опрашивай его значение по мере выполнения ThreadProc, аккуратно на него реагируя и вызывая в ThreadProc EndThread когда это необходимо, а можешь не изобретать велосипед, а воспользоваться классом TThread, в котором это уже все сделано

>Palladin © (27.02.08 15:32) [12]
да в том то и дело, что хотел разобратся с BeginThread и EndThread.
Так что велик я не изабретаю.-)
Вывешивать флажки — не очень мне нравится, разве для моего случая это панацея?
Тогда вид процедуры потока будет такой
procedure ThreadProc;
var
ExitCode:Cardinal;
begin
while not Флажок do
begin


> tytus (27.02.08 15:38) [13]

от именно, такой и будет


> Вывешивать флажки — не очень мне нравится, разве для моего
> случая это панацея?

это не панацея, а правильное решение


> GetExitCodeThread(hThread,ExitCode);

вот только это бред.

хотя есть и другой вариант, организовать в ThreadProc цикл обработки сообщений


> да в том то и дело, что хотел разобратся с BeginThread и
> EndThread.

так а в чем проблеммы, идешь и изучаешь TThread.

>Palladin © (27.02.08 15:42) [15]
вот только это бред.
пачему бред. только об этом говорили.
и как же без ExitCode выполнить EndThread в процедуре потока.

>хотя есть и другой вариант
это что — PostMessage(hThread, CX_МойМессаг,0,0) — так что-ли.
Если да, тогда зачем услажнять жизнь самому себе.


> хотел разобратся с BeginThread и EndThread

Скажу тебе по великому секрету — если ты собрался использовать BeginThread, то EndThread, как правило, нафиг не нужен, и даже опасен в ряде ситуаций.

Вот шаблон поточной ф-ции, которым можно обойтись во многих (но не в любой) ситуациях:
function ThreadProc(параметр): Integer;
begin
while not Флажок do
begin

нет, я так не могу :) держите меня семеро!


> и как же без ExitCode выполнить EndThread в процедуре потока

еще раз объясняю, EndThread завершает поток в контексте которого он выполняется. и параметром ему передается чиселко которое ты потом получишь с помощью процедуры GetExitCodeThread(hThread,ExitCode) но только уже из другого потока

а выполняется он очень просто пишется в ThreadProc
EndThread(2 Или 3);

а потом уже в Button2Click
при помощи вызова
GetExitCodeThread(hThread,ExitCode)
ты получишь в ExitCode свое 2 или 3 которое указал в EndThread

если ты и сейчас не понял, то я уже не знаю что с тобой делать, только что за метлой послать

>Palladin © (27.02.08 15:54) [19]
GetExitCodeThread(hThread,ExitCode)
ты получишь в ExitCode свое 2 или 3 которое указал в EndThread

нихрена , батенька, всегда вазвращает STILL_ACTIVE;
а чтоб сего небыло, нужна написать:
Флажок:=false;//или true, кому как нравиццо
while WaitForSingleObject(hThread,50)<>WAIT_OBJECT_0 do
Application.ProcessMessages;

а уже потом GetExitCodeThread, тогда только мы получим 2 или 3
Вообщето, всем списибо. Я немного нето думал. Точнее что GetExitCodeThread нужна чтобы потом ExitCode использовать в EndThread.
А оказалось, что GetExitCodeThread — нужна всего лишь для получения результата выполнения ф-ции потока. А EndThread нужно вызывать в процедуре потока, и передавать в нее нужное.

>Сергей М. © (27.02.08 15:52) [18]
function ThreadProc(параметр): Integer;
begin
while not Флажок do
begin

end;
Result := КодВозврата;//2
EndThread(2);
end;
Что в Result писать 2 (без EndThread), что в EndThread писать 2 (без Result) -один хрен, возвращает 2. Так что, писать так:
Result := 2;//для GetExitCodeThread
EndThread(2);//чтобы поток убился.

>All
про WaitForSingleObject наверное умолчали, чтобы потом добить меня основательно.


> Что в Result писать 2 (без EndThread), что в EndThread писать
> 2 (без Result) -один хрен, возвращает 2

Я не про возврат)

То что «один хрен возвращает», то и ежу понятно)

Я про бездумное использование EndThread. Пока ты демонстрируешь именно оное, причем с завидным ослиным упрямством при этом)


> про WaitForSingleObject наверное умолчали

А кому он интересен ?)

В твоем вопросе не было ни намека на синхронизацию, речь зашла про «в чем тут дело» и логику/правильность использования ф-ции EndThread)

а чего ты хотел? никто тебе здесь полного решения твоей хотелки и не давал вообщето, тебе лишь объясняли что такое EndThread и GetThreadExitCodeThread, так что не понятно какие могут быть претензии

>Сергей М. © (27.02.08 16:32) [21]
так что, после WaitFor SingleObject писать CloseHandle(hThread); и все?
Поток корректно убъецца?
причем с завидным ослиным упрямством
так что, EndThread в моем примере ненужен?

>Palladin © (27.02.08 16:37) [22]
твоей хотелки
тут форум программистов, а не улица, папрашу. =) .

всем спасибо исче раз, объяснили .

чесс слово, хочешь узнать побольше о потоках на низком уровне, почитай Рихтера, узнаешь очень много полезного и масса вопросов сразу исчезнет, это не тот случай что бы ставить эксперименты

так же узнаешь, что кроме WaitForSingleObject, существует другая масса вкусных функций синхронизации, а точнее объектов ядра для синхронизации и контроля потоков

>Palladin © (27.02.08 17:10) [24]
а рихтер по-моему написал для Си.
У меня есть его книга
Создание эффективных WIN32-приложений
с учетом специфики 64-разрядной версии Windows

в формате chm, зокачал аткуда-то. но что-то неинтересно вникать когда код неродной -)
Если есть Рихтер на Delphi — скинь сцылку, плз.


> после WaitFor SingleObject писать CloseHandle(hThread);
> и все?
> Поток корректно убъецца?

Поток корректно «умирает», когда штатно завершилось (или принудительно терминировано) выполнение поточной функции и закрыты все хендлы, ассоциированные с созданным потоком.

Корректность же с т.з. ресурсов, использованных потоком в ходе выполнения поточной ф-ции, — это отдельная песня.


> EndThread в моем примере ненужен?

В твоем точно не нужен).. Но на то он и пример, а не реальный «боевой» код)

>Palladin © (27.02.08 17:11) [25]
а мне сия ф-я (функция) панравилась в паре с ShellExecuteEx, когда нужно подождать выполнение другой программы (например я юзал WinRar с передачей параметров извлечения файлов. )


> tytus (27.02.08 17:14) [26]

при чем тут C ? он тебе описывает что и как происходит, а на C это или на чем то другом, абсолютно по барабану, потому что это все на WinAPI, а он для всех одинаковый и названия функций и соглашения о типах и вызовах поддерживаются любой средой разработки для Win32. ну акромя самых дубовых. я таких даже не знаю

Дарю грабли, познакомься с ними поближе:

function MyRakeFunc(..): Integer;
var
arr: array of SomeType;
begin
SetLength(arr, 1);
EndThread(SomeCode);
end;

>Palladin © (27.02.08 17:20) [29]
не. ну яж не утверждал что если на сях, то это критично. Просто, говорю, привык читать текст, а иже с ним и примеры на делфи. Абезательно пачитаю.

>Сергей М. © (27.02.08 17:21) [30]
пока что ничего сказать немогу. могу предпалажить, что массив останется в памяти, после того как поток умрет..
Пачитаю Рихтера, наберусь ума-разуму и абезательно напишу. -)

Еще букварь какой-нить по великому-могучему не забудь почитать, наберись ума-разума и больше не коверкай слова — тут же тебе не пацанская пивная «туса»)

>Сергей М. © (27.02.08 17:48) [32]
-) абезательна пачитаю!
ктому-же, по сравнению с тем, что я читал на форумах на этом сайте, мои сообщения — детский лепет.

«туса» — а откуда вы такие славечки знаете -)))

тут ведь так — или все или никто. я за! Больше каверкать небуду.

Потоки и методы их синхронизаций в Delphi

Статья призвана дать понятия о процессах, потоках и принципах программирования многопоточных приложений в delphi.

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

Поток — объект внутри процесса, отвечающий за выполнение кода и получающий для этого процессорное время.

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

Каждый поток может создать другой поток и т.д. Потоки не могут существовать отдельно от процесса, т.е. каждый поток принадлежит какому-то процессу и этот поток выполняет код, только в адресном пространстве этого процесса. Иными словами, поток не может выполнить код чужого процесса, хотя в nt-системах есть лазейка, но это уже тема отдельной статьи.

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

В delphi существует специальный класс, реализующий потоки — tthread. Это базовый класс, от которого надо наследовать свой класс и переопределять метод execute.

tnew = class(tthread)
private
< private declarations >
protected
procedure execute; override;
end;

procedure tnew.execute;
begin
< place thread code here >
// Код, который будет выполняться в отдельном потоке
end;

Теперь можно в теле процедуры tnew.execute писать код, выполнение, которого подвешивало бы программу.

Тонкий момент. В теле процедуры не надо вызывать метод execute предка.

Теперь необходимо запустить поток. Как всякий класс tnew необходимо создать:

var
new: tnew;

begin
new := tnew.create(true);
end;

Значение true в методе create значит, что после создания класса поток автоматически запущен не будет.

Потом указываем, что после завершения кода потока он сразу завершится, т.е. не надо заботиться о его закрытии. В противном случае, необходимо самим вызывать функцию terminate.

Устанавливаем приоритет в одно из возможных значений:

tpidle Работает, когда система простаивает
tplowest Нижайший
tplower Низкий
tpnormal Нормальный
tphigher Высокий
tphighest Высочайший
tptimecritical Критический

Не рекомендую устанавливать слишком большой приоритет т.к. поток может существенно загрузить систему.

Тонкий момент. Если в потоке присутствует бесконечный цикл обработки чего-либо, то поток будет загружать систему под завязку. Чтобы избежать этого вставляйте функцию sleep(n), где n — количество миллисекунд, на которое поток приостановит свое выполнение, встретив это функцию. n следует выбирать в зависимости от решаемой задачи.

Кстати, если Вы планируйте писать код потока в отдельном модуле, то можно немного упростить написание скелета класса. Для этого выберите в хранилище объектов — thread object (Это на закладке new). Выскочит окно, в котором надо ввести имя класса, после чего, нажав Ок, автоматически создаться новый модуль со скелетом Вашего класса.

Синхронизация потоков при обращении к vcl-компонентам

Значит, мы научились создавать потоки. Но тут всплывает интересная вещь: что будет, если два потока обращаются к одним и тем же данным по записи? Например, два потока пытаются изменить заголовок главной формы.

Специально для этого в ОС реализованы механизмы синхронизаций. В частности, в классе tthread есть метод позволяющий избежать параллельного доступа к vcl-компонентам:

procedure synchronize(method: tthreadmethod);

Он то и позволяет избежать конфликта при обращении к одним vcl-компонентам разными потоками. В качестве параметра ему передается адрес процедуры без параметров. А как вызвать с параметрами? Для этого можно использовать внутриклассовые переменные.

tnew = class(tthread)
private
< private declarations >
st: string;
procedure update;
protected
procedure execute; override;
end;

var
new: tnew;

procedure update;
begin
form 1.caption := s;
end;

begin
s := ‘yes’;
synchronize(update);
end;

Вот полный пример, в котором метод addstr добавляет в memo несколько строчек. Если мы просто вызовем метод, то строчки от потоков будут добавятся в произвольном порядке. Если addstr вызовем методом synchronize, то строчки добавятся сначала от одного потока, а затем от второго. Получается, что поток монопольно захватывает ресурс memo и добавляет в него необходимую информацию, после добавления поток освобождает memo и вот теперь уже другой поток может добавлять в memo свои данные. Поменьше слов — побольше сурсов:

uses
windows, messages, sysutils, variants, classes, graphics, controls, form s, dialogs, stdctrls;

type
t form 1 = class(t form )
memo1: tmemo;
button1: tbutton;
procedure button1click(sender: tobject);
private
< private declarations >
public
< public declarations >
end;

tnew = class(tthread)
private
s: string;
procedure addstr;
protected
procedure execute; override;
end;

var
form 1: t form 1;
new1, new2: tnew;

procedure t form 1.button1click(sender: tobject);
begin
new1 := tnew.create(true);
new1.freeonterminate := true;
new1.s := ‘1 thread’;
new1.priority := tplowest;
new2 := tnew.create(true);
new2.freeonterminate := true;
new2.s := ‘2 thread’;
new2.priority := tptimecritical;
new1.resume;
new2.resume;
end;

< tnew >
procedure tnew.addstr;
begin
form 1.memo1.lines.add(s);
sleep(2);
form 1.memo1.lines.add(s);
sleep(2);
form 1.memo1.lines.add(s);
sleep(2);
form 1.memo1.lines.add(s);
sleep(2);
form 1.memo1.lines.add(s);
end;

Илон Маск рекомендует:  Элемент label

procedure tnew.execute;
begin
synchronize(addstr); // Вызов метода с синхронизацией
//addstr; // Вызов метода без синхронизации
end;

Другие способы синхронизации. Модуль syncobjs

В модуле syncobjs находятся классы синхронизации, которые являются оберткой вызовов api-функций . Всего в этом модуле объявлено пять классов. tcriticalsection, tevent, а так же и более простая реализация класса tevent — tsimpleevent и используются для синхронизации потоков, остальные классы можно и не рассматривать. Вот иерархия классов в этом модуле:

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

Наиболее простым в понимании является tcriticalsection или критическая секция. Код, расположенный в критической секции, может выполняться только одним потоком. В принципе код ни как не выделяется, а происходит обращение к коду через критическую секцию. В начале кода находится функция входа в секцию, а по завершению его выход из секции. Если секция занята другим потоком, то потоки ждут, пока критическая секция не освободится.

В начале работы критическую секцию необходимо создать:

var
section: tcriticalsection; // глобальная переменная
begin
section.create;
end;

Допустим, имеется функция, в которой происходит добавление элементов в глобальный массив:

function addelem(i: integer);
var
n: integer;
begin
n := length(mas);
setlength(mas,n + 1);
mas[n + 1] := i;
end;

Допустим, эту функцию вызывают несколько потоков, поэтому, чтобы не было конфликта по данным можно использовать критическую секцию следующим образом:

function addelem(i: integer);
var
n: integer;
begin
section.enter;
n := length(mas);
setlength(mas,n + 1);
mas[n + 1] := i;
section.leave;
end;

Уточню, что критических секций может быть несколько. Поэтому при использовании нескольких функций, в которых могут быть конфликты по данным надо для каждой функции создавать свою критическую секцию. После окончания их использования, когда функции больше не будут вызываться, секции необходимо уничтожить.

Как Вы поняли, очень надеюсь, что вход и выход из критической секции не обязательно должен находиться в одной функции. Вход обозначает, только то, что другой поток встретив вход и обнаружив его занятость, будет приостановлен. А выход просто освобождает вход. Совсем просто, критическую секцию можно представит как узкую трубу на один поток, как только поток подходит к трубе, он заглядывает в нее и если видит, что через трубу уже кто-то лезет, будет ждать, пока другой не вылезет.

А вот и пример, в котором происходит добавление элемента в динамический массив. Функция sleep добавляет задержку в цикл, что позволяет наглядно увидеть конфликт по данным, если Вы, конечно, уберете вход и выход из критической секции в коде.

uses
windows, messages, sysutils, variants, classes, graphics, controls, form s, dialogs, stdctrls, syncobjs;

type
t form 1 = class(t form )
button1: tbutton;
memo1: tmemo;
procedure form create(sender: tobject);
procedure form destroy(sender: tobject);
procedure button1click(sender: tobject);
private
< private declarations >
public
< public declarations >
end;

tnew = class(tthread)
protected
procedure execute; override;
end;

var
form 1: t form 1;
cs: tcriticalsection;
new1, new2: tnew;
mas: array of integer;

procedure t form 1. form create(sender: tobject);
begin
setlength(mas,1);
mas[0] := 6;
// Создаем критическую секцию
cs := tcriticalsection.create;
end;

procedure t form 1. form destroy(sender: tobject);
begin
// Удаляем критическую секцию
cs.free;
end;

< tnew >
procedure tnew.execute;
var
i: integer;
n: integer;
begin
for i := 1 to 10 do
begin
// Вход в критическую секцию
cs.enter;
// Код, выполнение которого параллельно запрещено
n := length(mas);
form 1.memo1.lines.add(inttostr(mas[n-1]));
sleep(5);
setlength(mas,n+1);
mas[n] := mas[n-1]+1;
// Выход из критической секции
cs.leave;
end;
end;

procedure t form 1.button1click(sender: tobject);
begin
new1 := tnew.create(true);
new1.freeonterminate := true;
new1.priority := tpidle;
new2 := tnew.create(true);
new2.freeonterminate := true;
new2.priority := tptimecritical;
new1.resume;
new2.resume;
end;

Для начала не много о wait-функциях. Это функции, которые приостанавливают выполнение потока. Частным случаем wait-функции является sleep, в качестве аргумента передается количество миллисекунд, на которое требуется заморозить или приостановит поток.

Тонкий момент. Если вызвать sleep(0), то поток, откажется от своего такта — процессорного времени и тут же встанет в очередь с готовностью на выполнение.

Полной wait-функции в качестве параметров передается дескрипторы потока(ов). Я не буду останавливаться на них сейчас подробно. В принципе, wait-функции инкапсулируют некоторые классы синхронизации в явном виде, остальные в не явном виде.

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

Все происходит следующим образом. Если событие установлено, то работать дальше можно, если событие сброшено, то все остальные потоки ждут. Различие между событиями и критическими секциями в то, что события проверяются в коде самого потока и используется wait-функция в явном виде. Если в критической секции wait-функция выполнялась автоматически, то при использовании событий необходимо вызывать ее для заморозки потока.

События бывают с автосбросом и без автосброса. С автосбросом значит, что сразу после возврата из wait-функции событие сбрасывается. При использовании событий без автосброса необходимо самим сбрасывать их.

Событием без автосброса удобно делать паузу в каком-то определенном участке кода потока. Просто сделать паузу в потоке, когда не имеет значения, где произойдет заморозка можно использовать метод tthread.susp end. События с автосбросом можно использовать, так же как и критические секции.

Для начала событие необходимо создать и желательно до того как будут созданы потоки их использующие, хотя точнее до вызова wait-функции.

create(eventattributes: psecurityattributes; manualreset, initialstate: boolean; const name: string);

eventattributes — берем nil.
manualreset — автосброс — false, без автосброса — true.
initialstate — начальное состояние true — установленное, false — сброшенное.
const name — имя события, ставим пустое. Событие с именем нужно при обмене данных между процессами.

var
event: tevent;
new1, new2: tnew; // потоки

begin
event := tevent.create(nil, false, false, »);
end;
procedure tnew.execute;
var
n: integer;
begin
event.waitfor(infinite);
n := length(mas);
setlength(mas,n + 1);
mas[n + 1] := i;
event.setevent;
end;

Все теперь ошибки не будет.

Более простым в использовании является класс tsimpleevent, который является наследником tevent и отличается от него только тем, что его конструктор вызывает конструктор предка сразу с установленными параметрами:

create(nil, true, false, »);

Фактически, tsimpleevent есть событие без автосброса, со сброшенным состоянием и без имени.

Следующий пример показывает, как приостановить выполнение потока в определенном месте. В данном примере на форме находятся три progressbar, поток заполняет progressbar. При желании можно приостановить и возобновить заполнение progressbar. Как Вы поняли мы будем создавать событие без автосброса. Хотя тут уместнее использовать tsimpleevent, мы использовали tevent, т.к. освоив работу с tevent будет просто перейти на tsimpleevent.

uses
windows, messages, sysutils, variants, classes, graphics, controls, form s, dialogs, stdctrls, syncobjs, comctrls;

type
t form 1 = class(t form )
button1: tbutton;
progressbar1: tprogressbar;
progressbar2: tprogressbar;
progressbar3: tprogressbar;
button2: tbutton;
procedure form create(sender: tobject);
procedure form destroy(sender: tobject);
procedure button1click(sender: tobject);
procedure button2click(sender: tobject);
private
< private declarations >
public
< public declarations >
end;

tnew = class(tthread)
protected
procedure execute; override;
end;

var
form 1: t form 1;
new: tnew;
event: tevent;

procedure t form 1. form create(sender: tobject);
begin
// Создаем событие до того как будем его использовать
event := tevent.create(nil,true,true,»);
// Запускаем поток
new := tnew.create(true);
new.freeonterminate := true;
new.priority := tplowest;
new.resume;
end;

procedure t form 1. form destroy(sender: tobject);
begin
// Удаляем событие
event.free;
end;

< tnew >
procedure tnew.execute;
var
n: integer;
begin
n := 0;
while true do
begin
// wait-функция
event.waitfor(infinite);
if n > 99 then
n := 0;
// Одновременно приращиваем
form 1.progressbar1.position := n;
form 1.progressbar2.position := n;
form 1.progressbar3.position := n;
// задержка для видимости
sleep(100);
inc(n)
end;
end;

procedure t form 1.button1click(sender: tobject);
begin
// Устанавливаем событие
// wait-функция будет фозвращать управление сразу
event.setevent;
end;

procedure t form 1.button2click(sender: tobject);
begin
// wait-функция блокирует выполнение кода потока
event.resetevent;
end;

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

uses
windows, messages, sysutils, variants, classes, graphics, controls, form s, dialogs, stdctrls, syncobjs, comctrls;

type
t form 1 = class(t form )
label 1: tlabel;
procedure form create(sender: tobject);
procedure form destroy(sender: tobject);
private
< private declarations >
public
< public declarations >
end;

tproc = class(tthread)
protected
procedure execute; override;
end;

tsend = class(tthread)
protected
procedure execute; override;
end;

var
form 1: t form 1;
proc: tproc;
send: ts end;
event: tevent;

procedure t form 1. form create(sender: tobject);
begin
// Создаем событие до того как будем его использовать
event := tevent.create(nil,false,true,»);
// Запускаем потоки
proc := tproc.create(true);
proc.freeonterminate := true;
proc.priority := tplowest;
proc.resume;
send := ts end. create(true);
s end. freeonterminate := true;
s end. priority := tplowest;
s end. resume;
end;

procedure t form 1. form destroy(sender: tobject);
begin
// Удаляем событие
event.free;
end;

< tnew >
procedure tproc.execute;
begin
while true do
begin
// wait-функция
event.waitfor(infinite);
form 1.label1.caption := ‘proccessing. ‘;
sleep(2000);
// Подготовка данных
//.
// разрешаем работать другому потоку
event.setevent;
end;
end;

< tsend >
procedure ts end. execute;
begin
while true do
begin
// wait-функция
event.waitfor(infinite);
form 1.label1.caption := ‘sending. ‘;
sleep(2000);
// Отсылка данных
//.
// разрешаем работать другому потоку
event.setevent;
end;
end;

Вот и все объекты синхронизации модуля syncobjs, которых в принципе хватит для решения различных задач. В windows существуют другие объекты синхронизации, которые тоже можно использовать в delphi, но уже на уровне api. Это мьютексы — mutex, семафоры — semaphore и ожидаемые таймеры.

Delphi: потоки

КОМПЬЮТЕРНЫЕ КУРСЫ «ПОИСК»

Пример простого многопоточного приложения

Поместите на главную форму три компонента TProgressBar, присвоив всем трем вертикальную ориентацию Orientation = pbVertical. Свойству Max, описывающему максимальное значение шкалы, назначим значение 1000. Под каждым компонентом TProgressBar поместите элемент управления TCheckBox (флажок). В правой части формы разместите три элемента TTrackBar (ползунок). У соответствующих компонентов TCheckBox и TTrackBar поменяйте значение свойства tag:

  • CheckBox1 и TrackBar1: Tag = 1
  • CheckBox2 и TrackBar2: Tag = 2
  • CheckBox3 и TrackBar3: Tag = 3

Последние три компонента проекта – метки TLabel – разместите над шкалами TProgressBar

Последние три компонента проекта – метки TLabel – разместите над шкалами TProgressBar

При помощи элементов управления TTrackBar мы будем изменять приоритеты соответствующих потоков. Одновременно выберите все компоненты TTrackBar и их свойству Max присвойте значение 3. Значение 0 будет соответствовать минимальному приоритету tpIdle, значение 3 – нормальному приоритету tpNormal.

С настройкой элементов управления покончено. Займемся потоками. Для этого выберите пункт меню File → New → Other.

В окне New Items в разделе Delphi Files выберите пиктограмму Thread Object и нажмите ОК. Назовите класс создаваемого потока TMyThread. Вновь созданный модуль с шаблоном кода потока сохраните под именем ThreadUnit.pas.

В соответствии с приведенным ниже листингом внесите изменения в модуль ThreadUnit:

В секции public потока опубликованы два поля ProgressBar : TProgressBar и Lbl : TLabel, предназначенные для организации взаимодействия потока с размещенными на главной форме шкалой и меткой. Основной метод Execute() выполняется до тех пор, пока он не будет разрушен вызовом функции Terminate. Внутри метода реализован цикл while I

В момент создания потоков мы связываем их с элементами управления TProgressBar и TLabel. Обращаю внимание, что все три экземпляра потоков создаются приостановленными и стартуют практически вместе после того, как к ним будут применены все настройки.

Чтобы не забыть, сразу опишем событие закрытия формы, в которой последовательно разрушим все три потока:

Настал час элемента управления TTrackBar. Задача компонента устанавливать приоритет для соответствующего потока. Выберите любой из этих компонентов и следующим образом опишите его обработчик события OnChange():

Назначьте описанный выше обработчик события OnChange() общим для всех компонентов TTrackBar.

Опишите обработчик события OnClick() любого из компонентов TCheckBox в соответствии с указанным ниже кодом и сделайте его общим для всех компонентов-флажков. Задача этой процедуры – приостановить/продолжить выполнение потока.

Сделайте этот обработчик общим для всех элементов TCheckBox. Приложение готово.

Источник: Д.Осипов — Delphi. Профессиональное программирование.

Исходный код примера здесь. Выполнен на Delphi XE.

EndThread — Процедура Delphi

выглядит это примерно так:

procedure TMyThread.Execute;
var
.
begin
.
repeat
. (работа с БД)
until Terminated;
end;

поток описан в модуле uMyThread.pas
этот модуль используется в модуле fMain.pas (юнит главной формы)

В процессе работы программы может быть момент, в который необходимо остановить поток и изменить «настройки» доступа к БД(изменение настроек — присвоение новой ConnectionString в ADOConection). Происходить это должно, например, по нажатию кнопки.

Пробовал 2 варианта:

1. Делаю MyThread.Terminate, потом получается сразу присвоение новой ConnectionString, а в этот момент еще не завершилась транзакция с предыдущей ConnectionString. Вылезает ошибка: «Операция не допускается, если объект открыт».

2. После MyThread.Terminate вызываю MyThread.WaitFor (чтобы цикл докрутился в последний раз). В этом случае (иду по шагам в цикле) происходит выполнение всего, что в цикле, затем проходится условие «until Terminated», после этого завершается выполнение TMyThread.Execute. И в этот момент вылезает exception EThread с сообщением «Неверный дескриптор».

В первом варианте вроде все понятно, а что со вторым — не могу понять.

И, соответственно, вопрос, как «по-правильному» дождаться завершения выполнения потока, чтобы «иметь полный доступ ко всему, что поток использует»?

Читал в одной книжке — понял, что надо вызывать WaitFor, но может есть какие-то особенности?

Заранее благодарен.

1. Kid_Deceiver , 01.09.2005 14:31
WaitFor
OnTerminate

2. Elecmek , 01.09.2005 18:17
Griff0n
У TThread есть свойство FreeOnTerminate, проверьте, что он стоит в false. Т.к. если там true, то объект удаляется сразу после завершения Execute, это как раз и может приводить к ошибкам при выполнении WaitFor.
3. Griff0n , 02.09.2005 00:42
Elecmek

да, точно, именно FreeOnTerminate по недосмотру (осталось от предыдущей идеи) стояло в TRUE.

Библиотека OmniThreadLibrary — простая многопоточность в среде Delphi

Написать интересную статью на техническую тему очень сложно. Приходится балансировать между тем, чтобы не скатиться в технические дебри и тем, чтобы совсем ничего не сказать. Сегодня я попробую в общих словах (без деталей) поговорить о том, как обстоят дела с разработкой многопоточных desktop-приложений в не столь популярной на сегодняшний день, но наверняка знакомой многим российским разработчикам среде Delphi. Статья ориентирована на НЕ новичков в программировании, являющихся при этом новичками в области создания многопоточных приложений.

Затронутая в заголовке тема очень обширна. Все, что будет написано ниже, — это даже не верхушка айсберга, это скорее полет на высоте 10000 метров над океаном, в котором эти айсберги плавают. Зачем писать такую статью? Скорее для того, чтобы обратить внимание на широкие возможности, которые уже давно доступны, но которых почему-то многие побаиваются и сторонятся.

Почему Delphi?

Я программирую на Delphi очень давно и не перестаю наслаждаться. Это во многих отношениях замечательный язык. Его уникальность в том, что он одновременно позволяет создавать код сколь угодно высокого уровня, при этом оставаясь «близким к железу», т.к. на выходе мы получаем native-приложение, а не код для виртуальной машины Java или .Net. И при этом язык Delphi очень прост и лаконичен, код на нем приятно читать и в нем достаточно легко разобраться, чего не могу сказать о коде на C или C++ (при всем моем великом уважении к разработчикам на C, хотя кто-то скажет, что это лишь дело привычки).
В настоящий момент Delphi утратил былую популярность. Вероятно, произошло это из-за того, что в 2000-х годах данный продукт был на несколько лет практически заброшен разработчиками, в результате чего он на какое-то время выпал из конкурентной гонки сред разработки. Действительно, после Delphi 7, выпущенного фирмой Borland в 2002-м году, более менее стабильный продукт появился лишь в 2007-м. Это был CodeGear Delphi 2007, выпущенный фирмой CodeGear, являющейся дочерней компанией Borland. Все версии между Delphi 7 и Delphi 2007 были практически непригодны к использованию. В 2008-м Borland продала подразделение CodeGear фирме Embarcadero Technologies, которая (за что ей особое спасибо!) незамедлительно начала превращать то, что ей досталось, в современную качественную среду разработки. Актуальной версией Delphi на момент написания статьи является Embarcadero Delphi XE2, выпущенная в сентябре 2011 года. Благодаря достаточно высокому качеству последних версий Delphi, данная среда разработки постепенно отыгрывает утраченные позиции.

Зачем нам многопоточность?

Люди хотели выполнять на компьютере несколько задач одновременно. Это называют многозадачностью. Реализуется многозадачность средствами операционной системы. Но если ОС умеет выполнять одновременно несколько приложений, почему бы и одному приложению внутри себя тоже не выполнять сразу несколько задач. Например, при архивации большого списка файлов архиватор может одновременно читать следующий файл, в это время в памяти архивировать текущий прочитанный и записывать результат в выходной файл на диске. Т.е. вместо того, чтобы в одном потоке выполнять над каждым файлом последовательно действия «прочитать» -> «заархивировать» -> «записать результат на диск», можно запустить 3 потока, один из которых будет читать файлы в память, второй поток — архивировать, а третий — сохранять на диск. Другим примером является выполнение в фоне какой-то малоприоритетной задачи — например, фоновое сохранение резервной копии файла, открытого в текстовом редакторе.
Если бы процессоры продолжали увеличивать свою тактовую частоту теми же темпами, как это происходило в 90-х и начале 2000-х годов, можно было бы не заморачиваться с многопоточностью и продолжать писать классический однопоточный код. Однако в последние годы процессоры перестали активно увеличивать скорость одного ядра, но зато начали наращивать количество самих этих ядер. Чтобы использовать потенциал современных процессоров на 100% без многопоточности просто не обойтись.

Почему сложно писать многопоточный код?

1) Легко допустить ошибку.
Когда на компьютере выполняется одновременно несколько приложений, адресное пространство (память) каждого процесса надежно изолировано от других процессов операционной системой и влезть в чужое адресное пространство довольно сложно. С потоками внутри одного процесса наоборот — все они работают с общим адресным пространством процесса и могут изменять его произвольным образом. Поэтому в многопоточном приложении приходится самостоятельно реализовывать защиту памяти и синхронизацию потоков, что приводит к необходимости написания относительно сложного, но при этом не несущего полезной нагрузки кода. Такой код называют «boilerplate» (сковородка), потому что сковородку надо сначала приготовить перед тем, как начнешь на ней что-то жарить. Именно необходимость написания «нестандартного» boilerplate-кода сдерживает развитие многопоточных вычислений. Для синхронизации потоков предусмотрено множество специальных механизмов: потокозащищенные (interlocked) команды процессора, объекты синхронизации операционной системы (критические секции, мьютексы, семаформы, события и т.п.), spin locks и т.д.
2) Код многопоточного приложения сложно анализировать.
Одна из сложностей многопоточного приложения состоит в том, что просматривая код многопоточного приложения визуально не понятно, может ли какой-то конкретный метод вызываться (и вызывается ли) из разных потоков. Т.е. вам придется держать в голове, какие методы могут вызываться из разных потоков, а какие нет. Поскольку делать абсолютно все методы потокозащищенными — это не вариант, всегда есть шанс нарваться на ошибку, вызвав из нескольких потоков метод, не являющийся потокозащищенным.
3) Многопоточное приложение сложно отлаживать.
В многопоточном приложении множество ошибок может возникать при определенном состоянии параллельно выполняющихся потоков (как правило, при последовательности команд, выполненных в разных потоках). Интересный пример описан тут (http://www.thedelphigeek.com/2011/08/multithreading-is-hard.html). Воссоздать такую ситуацию искусственно зачастую очень сложно, практически нереально. К тому же в Delphi инструментов для отладки многопоточных приложений не очень много, Visual Studio в этом плане явный лидер.
4) В многопоточном приложении сложно обрабатывать ошибки.
Если приложение имеет графический интерфейс пользователя, то взаимодействовать с пользователем может только один поток. Обычно, когда в приложении происходит какая-то ошибка, мы либо обрабатываем ее внутри приложения, либо показываем сообщение пользователю. Если же ошибка происходит в дополнительном потоке, он не может ничего сказать пользователю «немедленно». Соответственно, приходится сохранять ошибку, произошедшую в дополнительном потоке, до момента его синхронизации с основным потоком и лишь потом выдавать пользователю. Это может приводить к относительно сложной и запутанной структуре кода.

Есть ли способ хоть немного упростить себе жизнь?

Представляю вашему вниманию OmniThreadLibrary (сокращенно OTL). OmniThreadLibrary — это библиотека для создания многопоточных приложений в Delphi. Ее автор — Primoz Gabrijelcic из Словении — непревзойденный профессионал с многолетним стажем разработки приложений на Delphi. OmniThreadLibrary — это абсолютно бесплатная библиотека с открытыми исходными кодами. В настоящий момент библиотека находится уже в достаточно зрелой стадии и вполне пригодна для использования в серьезных проектах.

Где найти информацию по OTL?

  • На этом форуме.
  • В блоге автора библиотеки.
  • На странице проекта в GoogleCode.

Также автор библиотеки сейчас занимается наполнением wiki-книги про OmniThreadLibrary и многопоточность, уже готовы статьи про большинство высокоуровневых примитивов OTL.

Какие возможности предоставляет OTL?

Данная библиотека содержит в себе низкоуровневые и высокоуровневые классы, позволяющие упрощенно управлять многопоточностью, не вдаваясь в подробности процессов создания/освобождения/синхронизации потоков на уровне WinAPI.
Особенный интерес представляют высокоуровневые примитивы для упрощенного управления многопточностью. Они примечательны тем, что их сравнительно легко интегрировать в готовое однопоточное приложение, практически не меняя структуры исходного кода. Данные примитивы позволяют создавать многопоточные приложения, концентрируясь на полезном коде приложения, а не на вспомогательном коде для управления многопоточностью.
К основным высокоуровневым примитивам относятся Future (асинхронная функция), Pipeline (конвейер), Join (параллельный вызов нескольких методов), ForkJoin (рекурсия с параллелизмом), Async (асинхронный метод), ForEach (параллельный цикл).
На мой взгляд, самыми интересными и полезными примитивами являются Future и Pipeline, т.к. для их использования имеющиеся код почти не нужно переписывать.

Future

Данный примитив позволяет выполнить асинхронный вызов функции и в нужный момент дождаться завершения расчета и получить результат выполнения. С помощью данного примитива вызов любой процедуры или функции можно безболезненно превратить в асинхронный.
Выглядит это примерно так:

Обратите внимание, что именно обращение к vFuture.Value является моментом синхронизации основного потока с дополнительным, т.е. до тех пор, пока мы не обратимся к Value мы вообще ничего не знаем о состоянии другого потока. Как только мы вызвали Value, основной поток приостанавливается до момента завершения расчета в дополнительном потоке.

Если требуется, можно в основном потоке реализовать неблокирующее ожидание результата:

Таким образом, примитив Future позволяет асинхронно выполнить какую-то задачу и вернуть результат в основной поток именно в тот момент, в который он там потребуется.

Pipeline

Pipeline (конвейер) — это гораздо более мощный примитив по сравнению с Future.
Представьте, что некий алгоритм выполняется в цикле для множества элементов. Например, производится какая-то обработка файлов в каталоге. Однопоточная программа будет брать очередной файл, прочитывать его, выполнять какие-то действия и сохранять измененный файл на диск. Имея конвейер можно исходный алгоритм разделить на этапы (чтение, обработка, сохранение) и запустить эти этапы в параллельных потоках. В самом начале запустится лишь самый первый этап и прочитает первый файл. Как только чтение завершится, запустится второй этап и начнет обработку прочитанного файла или его порции (если первый этап читает файлы не целиком а порциями). В это время первый этап уже начнет читать второй файл. Как только второй этап обработает первый файл, подключится третий этап и начнет сохранение. В этот момент мы получим состояние, при котором все три этапа работают параллельно.
Пример для Pipeline, близкий к реальной жизни, слишком загрузил бы статью, поэтому для иллюстрации использования Pipeline ограничиваюсь копией абсолютно синтетического примера из OtlBook (чур сильно не бить!):

В данном примере первый этап генерирует миллион чисел, передавая их по одному на следующий этап. Второй этап умножает каждое число на 3 и передает на третий этап. Третий этап суммирует результаты и возвращает одно число. Каждый Stage выполняется в своем потоке. Более того, Otl позволяет указать, какое число потоков для каждого Stage’а использовать (если одного мало) за счет простого модификатора .NumTasks(N). Возможности OTL действительно очень широки.

Базовым классом для поддержки обмена данными между этапами конвейера является класс потокозащищенной очереди — TOmniBlockingCollection. Данный класс позволяет нескольким потокам одновременно добавлять и считывать элементы. Высокая скорость работы коллекции достигается за счет хитрого управления памятью и применения блокировок на основе потокозащищенных инструкций процессора вместо блокировок на основе объектов синхронизации ОС. О подробностях реализации класса TOmniBlockingCollection можно почитать тут, тут и тут.

Заключение

Кто-то посмотрев на приведенные примеры скажет «да я все это уже видел». Действительно, в Task Parallel Library для .Net Framework 4 присутствуют примерно такие же классы. При этом существует ряд различий между тем, как исполняются потоки внутри машины .Net и как потоки исполняются на реальном процессоре. Рассмотрение данных различий — за пределами этой статьи. Я лишь хотел акцентировать внимание на замечательной библиотеке, и тех широких возможностях которые она предоставляет Delphi разработчикам. Хочу отметить, что библиотека снабжена большим количеством примеров, иллюстрирующих использование как низкоуровневых, так и высокоуровневых классов.

Чтобы развеять опасения по поводу зрелости и надежности данной библиотеки, скажу лишь, что за счет использования Pipeline в сложном коммерческом многопользовательском приложении (не web) удалось сократить время выполнения операции над группой файлов на клиенте почти в два раза за счет разнесения по отдельным потокам обработку файлов на клиенте и их передачу на сервер. Использовать ли связку Delphi + OmniThreadLibrary в ваших проектах — решать вам ;)

Работа с потоками в Delphi

Written on 10 Февраля 2009 . Posted in Delphi

Нередко встречал на форумах мнения, что потоки не нужны вообще, любую программу можно написать так, что она будет замечательно работать и без них. Конечно, если не делать ничего серьёзней «Hello World» это так и есть, но если постепенно набирать опыт, рано или поздно любой начинающий программист упрётся в возможности «плоского» кода, возникнет необходимость распараллелить задачи. А некоторые задачи вообще нельзя реализовать без использования потоков, например работа с сокетами, COM-портом, длительное ожидание каких-либо событий, и т.д.

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

Для создания дополнительных потоков в Delphi существует базовый класс TThread, от него мы и будем наследоваться при реализации своих потоков. Для того, чтобы создать «скелет» нового класса, можно выбрать в меню File — New — Thread Object, Delphi создаст новый модуль с заготовкой этого класса. Я же для наглядности опишу его в модуле формы. Как видите, в этой заготовке добавлен один метод — Execute. Именно его нам и нужно переопределить, код внутри него и будет работать в отдельном потоке. И так, попробуем написать пример — запустим в потоке бесконечный цикл:
Запустите пример на выполнение и нажмите кнопку. Вроде ничего не происходит — форма не зависла, реагирует на перемещения. На самом деле это не так — откройте диспетчер задач и вы увидите, что процессор загружен по-полной. Сейчас в процессе вашего приложения работает два потока — один был создан изначально, при запуске приложения. Второй, который так грузит процессор — мы создали по нажатию кнопки. Итак, давайте разберём, что же означает код в Button1Click:
тут мы создали экземпляр класса TNewThread. Конструктор Create имеет всего один параметр — CreateSuspended типа boolean, который указывает, запустить новый поток сразу после создания (если false), или дождаться команды (если true).
свойство FreeOnTerminate определяет, что поток после выполнения автоматически завершится, объект будет уничтожен, и нам не придётся его уничтожать вручную. В нашем примере это не имеет значения, так как сам по себе он никогда не завершится, но понадобится в следующих примерах.
Свойство Priority, если вы еще не догадались из названия, устанавливает приоритет потока. Да да, каждый поток в системе имеет свой приоритет. Если процессорного времени не хватает, система начинает распределять его согласно приоритетам потоков. Свойство Priority может принимать следующие значения:

  • tpTimeCritical — критический
  • tpHighest — очень высокий
  • tpHigher — высокий
  • tpNormal — средний
  • tpLower — низкий
  • tpLowest — очень низкий
  • tpIdle — поток работает во время простоя системы

Ставить высокие приоритеты потокам не стоит, если этого не требует задача, так как это сильно нагружает систему.
Ну и собственно, запуск потока.

Думаю, теперь вам понятно, как создаются потоки. Заметьте, ничего сложного. Но не всё так просто. Казалось бы — пишем любой код внутри метода 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 передаётся метод нашего потока, но сам он передаётся без параметров. Параметры можно передать, добавив поля нужного типа в описание нашего класса. У нас будет одно поле — тот самый прогресс:
Вот теперь ProgressBar двигается, и это вполне безопасно. А безопасно вот почему: процедура Synchronize на время приостанавливает выполнение нашего потока, и передаёт управление главному потоку, т.е. SetProgress выполняется в главном потоке. Это нужно запомнить, потому что некоторые допускают ошибки, выполняя внутри Synchronize длительную работу, при этом, что очевидно, форма зависает на длительное время. Поэтому используйте Synchronize для вывода информации — то самое двигание прогресса, обновления заголовков компонентов и т.д.

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

Надеюсь, вы поняли как работает Synchronize. Но есть еще один довольно удобный способ передать информацию форме — посылка сообщения. Давайте рассмотрим и его. Для этого объявим константу:
В объявление класса формы добавим новый метод, а затем и его реализацию:
Теперь мы немного изменим, можно сказать даже упростим, реализацию метода Execute нашего потока:
Используя функцию SendMessage, мы посылаем окну приложения сообщение, один из параметров которого содержит нужный нам прогресс. Сообщение становится в очередь, и согласно этой очереди будет обработано главным потоком, где и выполнится метод SetProgressPos. Но тут есть один нюанс: SendMessage, как и в случае с Synchronize, приостановит выполнение нашего потока, пока основной поток не обработает сообщение. Если использовать PostMessage этого не произойдёт, наш поток отправит сообщение и продолжит свою работу, а уж когда оно там обработается — неважно. Какую из этих функций использовать — решать вам, всё зависит от задачи.

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

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

Работают они следующим образом: внутри критической секции может работать только один поток, другие ждут его завершения. Чтобы лучше понять, везде приводят сравнение с узкой трубой: представьте, с одной стороны «толпятся» потоки, но в трубу может «пролезть» только один, а когда он «пролезет» — начнёт движение второй, и так по порядку. Еще проще понять это на примере и тем же ProgressBar’ом. Итак, запустите один из примеров, приведённых ранее. Нажмите на кнопку, подождите несколько секунд, а затем нажмите еще раз. Что происходит? ProgressBar начал прыгать. Прыгает потому, что у нас работает не один поток, а два, и каждый из них передаёт разные значения прогресса. Теперь немного переделаем код, в событии onCreate формы создадим критическую секцию:
У TCriticalSection есть два нужных нам метода, Enter и Leave, соответственно вход и выход из неё. Поместим наш код в критическую секцию:
Попробуйте запустить приложение и нажать несколько раз на кнопку, а потом посчитайте, сколько раз пройдёт прогресс. Понятно, в чем суть? Первый раз, нажимая на кнопку, мы создаём поток, он занимает критическую секцию и начинает работу. Нажимаем второй — создаётся второй поток, но критическая секция занята, и он ждёт, пока её не освободит первый. Третий, четвёртый — все пройдут только по-очереди.

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

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

EndThread — Процедура Delphi

Вне зависимости от того, хотим мы этого или нет, но прогресс движется дальше, появляются все новые технологии, новые процессоры, новые «высоты» производительности. В связи с этим, все чаще программистам приходится разрабатывать программы, которые используют сложные операции, в которых важна скорость и которые выполнялись бы одновременно с другими. Этому вопросу как раз и посвящена данная статья — создание многопоточных приложений (со множеством дочерних процессов, которые выполняются одновременно).

Зачем нужен Thread

Итак, зачем же нужен класс Thread и его потомки? Во-первых, этот объект позволяет создавать как бы несколько программ в одной (несколько процессов, или, потоков). Во-вторых, эти процессы могут выполняться как по очереди, так и одновременно (как запрограммирует разработчик). В-третьих, из этих процессов можно легко получить доступ ко всем глобальным данным программы, т.к. класс процесса является, по сути, просто частью программы — обычным юнитом (unit). В-четвертых, можно создать своих собственных потомков TThread и запустить сразу несколько экземпляров одного и того же созданного класса. В-пятых, каждым процессом очень легко управлять — запускать, завершать, приостанавливать, прерывать, устанавливать приоритетность, и т.д.

Краткое описание класса TThread

Итак, рассмотрим некоторые свойства, методы и события класса TThread.

Методы FreeOnTerminate— освобождать ли память, выделенную под экземпляр класса процесса, когда этот процесс завершается. Если True — при завершении процесса (или при вызове метода Terminate) экземпляр класса автоматически освобождается (аналогично вызову метода Free). Тип: Boolean;
Handle (Thread >Тип: THandle;
ReturnValue — возвращаемое значение при завершении процесса. Тип: Integer;
Priority — приоритет процесса. Возможные значения этого свойства мы разберем немного позже. Тип: TThreadPriority;
Suspended — показывает, в каком состоянии находится процесс: True — приостановлен, False — в нормальном. Тип: Boolean;
Terminated — показывает, завершен ли процесс. True — завершен, False — нет. Тип: Boolean;
Create(CreateSuspended: Boolean) — создает экземпляр класса. Параметр CreateSuspendedуказывает на то, нужно ли создавать приостановленную задачу ( True), или запускать ее сразу ( False);
Suspend — приостанавливает выполнение процесса;
Resume — продолжает выполнение процесса после Suspend;
Terminate — полностью прекращает выполнение процесса;
WaitFor — ждет завершения процесса, возвращая затем код его завершения (ReturnValue);
Synchronize(Method: TThreadMethod) — синхронизирует выполнение метода процесса, позволяя ему работать параллельно с другими процессами.
OnTerminate— возникает, когда процесс находится в стадии завершения.

Приоритет процесса

Итак, что же такое приоритет? Приоритет — это величина, определяющая, насколько данный процесс должен выполнятся быстрее по сравнению с другими. Т.е., другими словами, чем выше приоритет процесса, тем больше времени он отбирает у системы и других, параллельно работающих процессов. Далее разберем возможные значения свойства Priority класса TThread в порядке возрастания приоритета:

  • tp >tpLowest — на два пункта ниже нормального;
  • tpLower — на один пункт ниже нормального;
  • tpNormal — нормальный. Такой приоритет у большинства задач;
  • tpHigher — на один пункт выше нормального;
  • tpHighest — на два пункта выше нормального;
  • tpTimeCritical — самый высокий приоритет — занимает все время процессора и системы. Это приоритет для систем реального времени, для которых важна каждая секунда и даже малейшая задержка может привести к сбою. Будьте осторожны с этим приоритетом!

Практика и примеры

Приведем небольшой пример того, как можно создать отдельный процесс:

Пример 1. «Шаблон» для создания Thread-ов.

А теперь — замечательный пример для изучения Thread! В нижеследующем примере приложение создает два параллельно работающих процесса. Один Thread пытается установить флажок CheckBox1 в положение «включено» (Checked := True), а другой, передергивая первого — в «выключено».

Пример работы с компонентами Indy UDP (server, client) на Delphi

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

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

Собственно если что-то непонятно можно задать мне вопрос. А вот собственно и код:

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdUDPServer, IdBaseComponent, IdComponent, IdUDPBase,
IdUDPClient, IdSocketHandle;

type
TForm1 = class(TForm)
IdUDPClient1: TIdUDPClient;
IdUDPServer1: TIdUDPServer;
Button1: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
procedure IdUDPServer1UDPRead(AThread: TIdUDPListenerThread; AData: TBytes;
ABinding: TIdSocketHandle);
private
< Private declarations >
public
< Public declarations >
end;

var
Form1: TForm1;

<$R *.dfm>
[b]//Процедура отправки сообщения[/b]
procedure TForm1.Button1Click(Sender: TObject);
begin
try
> ;
IdUDPClient1.Connect;
if IdUDPClient1.Connected then
begin
IdUDPClient1.Send(TimeToStr(Time));
Label1.Caption := ‘ok’;
end;
> Beep;Beep;Beep;
except
MessageDlg(‘Что-то не получилось =(‘, mtError, [mbOk], 0);
end;
end;
[b]
//Вкл./Выкл. сервера UDP при запуске и закрытии формы[/b]
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
>end;

procedure TForm1.FormCreate(Sender: TObject);
begin
>end;

[b]//Процедура реакции сервера при получении данных[/b]
procedure TForm1.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
AData: TBytes; ABinding: TIdSocketHandle);
Var
i:Integer;
s:String;
begin
s := »;
try
i := 0;
while (AData[i] <> 0) do
begin
s := s chr(AData[i]);
i := i 1;
end;
finally
Label1.Caption := s;
end;
end;

Многократное Thread Delphi Stop

У меня есть кадр, в нем у меня есть нить, создание этого фронта и во время выполнения, каждый щелчок, который даст кнопку создает новый кадр, и может иметь бесчисленное множество в форме. проблема заключается в том, что . Я создаю первый, поток начинает нормально, когда я создаю вторую, первую нить к, и начинается второй, если я нажму снова, первый и второй стенд еще и третья начинается, если я закрыть третий, второй на работу, так как это происходит? Спасибо

Вы не используете TThread правильно. Вы не начинать нить (так что это не освобождает себя , когда прекращается), вы звоните Execute() прямо, вы Synchronize ИНГ все тело Execute() . Так Execute() работает в основном потоке, вызывая , ProcessMessages() чтобы новое нажатие кнопки, которая не называет Execute() блокирование предыдущего Execute() до новых Execute() выходов, и так далее. Вот почему вы испытываете симптомы , которые вы видите.

Чтобы это исправить, вам нужно сделать следующее:

В конструкторе потока, вызовите inherited Create(False) вместо этого. Это позволяет нить запуститься автоматически. В противном случае, вы должны вызвать поток Resume() или Start() метод после выхода конструктора.

удалить Execute() из Click() . Пусть выполняется вызов резьбы Execute() .

удалить ProcessMessages() из Teste() . Там никогда не нужно вызывать ProcessMessages() в потоке (если это не вызывается внутри Synchronize d- или Queue д код , работающий в основном потоке, но даже в этом случае его следует избегать , если это возможно).

только Synchronize() небольшие блоки кода , которые на самом деле нужны для запуска в главном потоке — код , который не работает в дополнительном потоке, код , который необходим для доступа к UI, код , который необходим для доступа к ресурсу совместно нескольких потоков и т.д. большой части вашего код нити не должен быть Synchronize d, что поражения цели с помощью резьбы на первом месте.

Первые шаги с TThread в Delphi

Первые шаги с TThread в Delphi

Введение

Вне зависимости от того, хотим мы этого или нет, но прогресс движется дальше, появляются все новые технологии, новые процессоры, новые «высоты» производительности. В связи с этим, все чаще программистам приходится разрабатывать программы, которые используют сложные операции, в которых важна скорость и которые выполнялись бы одновременно с другими. Этому вопросу как раз и посвящена данная статья — создание многопоточных приложений (со множеством дочерних процессов, которые выполняются одновременно).

Зачем нужен Thread

Итак, зачем же нужен класс Thread и его потомки? Во-первых, этот объект позволяет создавать как бы несколько программ в одной (несколько процессов, или, потоков). Во-вторых, эти процессы могут выполняться как по очереди, так и одновременно (как запрограммирует разработчик). В-третьих, из этих процессов можно легко получить доступ ко всем глобальным данным программы, т.к. класс процесса является, по сути, просто частью программы — обычным юнитом (unit). В-четвертых, можно создать своих собственных потомков TThread и запустить сразу несколько экземпляров одного и того же созданного класса. В-пятых, каждым процессом очень легко управлять — запускать, завершать, приостанавливать, прерывать, устанавливать приоритетность, и т.д.

Краткое описание класса TThread

Итак, рассмотрим некоторые свойства, методы и события класса TThread.

Методы FreeOnTerminate — освобождать ли память, выделенную под экземпляр класса процесса, когда этот процесс завершается. Если True — при завершении процесса (или при вызове метода Terminate) экземпляр класса автоматически освобождается (аналогично вызову метода Free). Тип: Boolean;
Handle (ThreadID) — дескриптор процесса. Эта величина может быть использована для управления процессом через функции WinAPI. Тип: THandle;
ReturnValue — возвращаемое значение при завершении процесса. Тип: Integer;
Priority — приоритет процесса. Возможные значения этого свойства мы разберем немного позже. Тип: TThreadPriority;
Suspended — показывает, в каком состоянии находится процесс: True — приостановлен, False — в нормальном. Тип: Boolean;
Terminated — показывает, завершен ли процесс. True — завершен, False — нет. Тип: Boolean;
Create(CreateSuspended: Boolean) — создает экземпляр класса. Параметр CreateSuspended указывает на то, нужно ли создавать приостановленную задачу (True), или запускать ее сразу (False);
Suspend — приостанавливает выполнение процесса;
Resume — продолжает выполнение процесса после Suspend;
Terminate — полностью прекращает выполнение процесса;
WaitFor — ждет завершения процесса, возвращая затем код его завершения (ReturnValue);
Synchronize(Method: TThreadMethod) — синхронизирует выполнение метода процесса, позволяя ему работать параллельно с другими процессами.
OnTerminate — возникает, когда процесс находится в стадии завершения.

Приоритет процесса

Итак, что же такое приоритет? Приоритет — это величина, определяющая, насколько данный процесс должен выполнятся быстрее по сравнению с другими. Т.е., другими словами, чем выше приоритет процесса, тем больше времени он отбирает у системы и других, параллельно работающих процессов. Далее разберем возможные значения свойства Priority класса TThread в порядке возрастания приоритета:

  • tpIdle — процесс выполняется только тогда, когда система не занята и больше нет работающих в данных момент процессов;
  • tpLowest — на два пункта ниже нормального;
  • tpLower — на один пункт ниже нормального;
  • tpNormal — нормальный. Такой приоритет у большинства задач;
  • tpHigher — на один пункт выше нормального;
  • tpHighest — на два пункта выше нормального;
  • tpTimeCritical — самый высокий приоритет — занимает все время процессора и системы. Это приоритет для систем реального времени, для которых важна каждая секунда и даже малейшая задержка может привести к сбою. Будьте осторожны с этим приоритетом!

Практика и примеры

Приведем небольшой пример того, как можно создать отдельный процесс:

Пример 1. «Шаблон» для создания Thread-ов.

А теперь — замечательный пример для изучения Thread! В нижеследующем примере приложение создает два параллельно работающих процесса. Один Thread пытается установить флажок CheckBox1 в положение «включено» (Checked := True), а другой, передергивая первого — в «выключено».

Пример 2. «Поединок» Thread-ов :-).

P.S. Отличный пример работы с TThread можно найти в подкаталоге Demos\Threads каталога, куда Вы установили Borland Delphi.

Эпилог

В этой статье отображены основные стороны работы с процессами (потоками) Thread в Borland Delphi. Если у Вас есть вопросы — скидывайте их мне на E-mail: snick@mailru.com, а еще лучше — пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!

Карих Николай. (Nitro) Московская область, г.Жуковский

Илон Маск рекомендует:  Что такое код pg_untrace
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL