Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Примечание. Потоки команд в среде разработки приложений

Потоки команд в среде разработки приложений. Создание потока. Класс потоков (Tthread). Синхронизация. Контролируемая остановка потока. Разрушение потока.

Каждый обработчик событий в Delphi является процедурой — с началом и концом. Важно не написать обработчик, который выполняется слишком долго, потому что в таком случае приложение покажется зависшим.

Например: создадим приложение, поместив компонент TButton на главную форму, и напишем код обработчика события OnClick.

procedure TForm1.Button1Click(Sender: TObject);

Var

X: double;

Begin

{$n+}

Repeat

X: =x+0.1

until x> 100000000;

Application.MessageBox ('Сделано', 'Работа завершена', MB_OK);

end;

После запуска программы и нажатия на кнопку, приложение некоторое время будет казаться зависшим. Его нельзя сдвинуть или изменить размер окна, а если поместить перед ним другое окно и затем его убрать, то форма не будет выглядеть нормально, т. е не перерисует себя. Но через некоторое время появляется панель с сообщением 'Сделано', и приложение вновь готово к работе.

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

Для решения таких проблем можно использовать метод — выполнять в каждом обработчике события только небольшой фрагмент работы.

В операционных системах семейства Windows каждая запущенная программа называется процессом. Процесс имеет дело в основном с вопросами разного рода собственности. Например, процесс владеет своим пространством в памяти. Что же касается исполняемого кода, то операционная система использует другой объект, называемый потоком (нитью). Когда запускается новый процесс, ему автоматически придается одна нить, которая начинает исполнять код соответствующей процедуры. Нити используются операционной системой для диспетчеризации времени процессора. Диспетчеризация — это метод выделения времени каждой из нитей (не процессов, поскольку процесс может иметь несколько нитей). Операционная система рассматривает все готовые к запуску нити и выбирает для исполнения одну из них. Диспетчер также определяет порцию времени, даваемую для исполнения каждой из нитей.

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

Например: программа поиска простых чисел с одной нитью.

procedure TForm1.Button1Click(Sender: TObject);

var

Low, High, Count, Count2: integer;

begin

// очистим ListBox1

ListBox1.Items.Clear;

// зададим диапазон

Low: = strtoint(edit1.Text);

High: = strtoint(Edit2.Text);

 

// найдем простые числа

for Count: = Low to High do

begin

Count2: = 2;

while (Count2 < Count) and not(Count mod Count2 = 0) do

inc(Count2);

// выведем в ListBox

 

if (Count=Count2) then

ListBox1.Items.Add (Inttostr (Count));

end {for}

end;

Программа работает неплохо при поиске простых чисел, не превосходящих 50000.

Если применить ее для поиска больших простых чисел, например, в диапазоне от 100000 до 110000, то получим «синдромом ложного зависания приложения».

Можно незначительно модернизировать приложение, отведя отдельный поток (нить) для выполнения арифметической работы. Тогда основной поток останется доступным для всех служебных функций, и приложение не будет казаться зависшим.

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

function DoFactor (NotUsed: Pointer): integer;

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

Чтобы создать саму нить, следует вызвать процедуру API Win32 с именем CreateThread. Для вызова требуется шесть аргументов; однако необходимы из них только два: адрес функции, с которой начинается новая нить, и переменная для хранения идентификатора нити.

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

Во-первых: переместить всю логику в новую функцию, которая послужит началом новой нити. Затем модифицировать все ссылки на объекты, чтобы они включали Form1. Например, компонент списка ListBox1окажется вне области действия, если идентифицировать его просто как ListBox1; поэтому нужно ссылаться на него как на Form1.ListBox1. Добавить вызов API CreateThread к обработчику события OnClick кнопки Buttonl. Вызов имеет вид

CreateThread (nil, 0, @DoFactor, nil, 0, THID);

Он означает, что вы хотите запустить новую нить. Информация, используемая для защиты, отсутствует, размер стека будет определяться динамически. Новая нить должна начинаться в функции DoFactor, не требующей аргумента. Никаких флагов не устанавливается; идентификатор нити помечется в переменную THID. Единственной непривычной вещью в этом вызове является @DoFactor. @ означает передачу адреса функции, а не ее вызов с последующим возвратом значения.

Например: Поиск простых чисел — вариант с несколькими нитями

function DoFactor (NotUsed: Pointer): integer;

var

Low, High, Count, Count2: integer;

begin

// очистим ListBox1

Form1.ListBox1.Items.Clear;

// зададим диапазон

Low: = strToInt(Form1.edit1.Text);

High: = strToInt(Form1.Edit2.Text);

 

// найдем простые числа

for Count: = Low to High do

begin

Count2: = 2;

while (Count2 < Count) and not(Count mod Count2 = 0) do

inc(Count2);

// выведем в ListBox

 

if (Count=Count2) then

Form1.ListBox1.Items.Add(inttostr(Count));

end {for}

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

THID: DWORD;

begin

CreateThread (nil, 0, @DoFactor, nil, 0, THID);

end; {procedure}

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

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

□ Если какие-то функции должны выполняться параллельно основному процессу, выделение потока обязательно.

□ Если какие-то расчеты идут достаточно долго, то многие считают, что их тоже нужно помещать в поток. Во время расчетов, программа блокируется и невозможно нажать кнопку Отмена или произвести какие-либо действия. Но поток тут абсолютно необязателен, потому что можно обойтись и без него. Достаточно внутри расчетов поставить ВЫЗОВ Application.ProcessMessages, и выполнение расчетов будет прерываться на некоторое время, а программа будет обслуживать другие сообщения, пришедшие от пользователя. Таким образом получится простой эффект многозадачности без использования потока.

□ Код критичен к времени выполнения. Допустим, что программа должна принимать какие-то данные по СОМ-порту. Как только в порт поступили данные, они должны быть моментально обработаны с минимальной задержкой. Обработку таких ситуаций желательно выносить в отдельный поток, потому что если в момент поступления данных программа занята большими расчетами, то данные могут оказаться, в лучшем случае, необработанными, в худшем — потерянными.

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

 

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

Разделение происходит в основном благодаря приоритету потока. У каждого потока есть приоритет, по которому определяется его важность. Чем выше приоритет, тем больше процессорного времени ему выделяется.

Потоки с одинаковым приоритетом будут получать одинаковое количество процессорного времени.

У дополнительных потоков приоритет выставляется такой же, как и у главного потока программы, но его можно увеличить, или уменьшить.

Для создания дополнительного потока в программах Delphi предназначен специальный модуль потока в репозитории (File\New\Other…) он обозначен пиктограммой Thread Object.

При выборе модуля Delphi запрашивает имя класса, который будет дочерним для основополагающего класса TThread.

Необходимость наследования связана с тем, что класс TThread содержит абстрактный метод Execute, который, собственно, и должен исполняться в рамках нового потока и который, следовательно, обязан перекрываться в потомках.

После указания имени дочернего класса Delphi раскрывает дополнительный модуль с обширным комментарием и заготовкой для дочернего класса.

{Важно: Методы и свойства объектов из библиотеки визуальных компонентов могут использоваться только в рамках вызова метода Synchronize, например:

Synchronize(UpdateCaption);

где метод UpdateCaption должен быть подобен такому

procedure MyThread.UpdateCaption;

Begin

Form1.Caption: = 'Новый текст метки';

end; }

{MyThread }

procedure MyThread.Execute;

Begin

{ Пожалуйста, поместите код потока в этом месте }

end;

End.

Программирование потока ничем не отличается от программирования обычной программы за одним важным исключением:

· поток не должен использовать методы и свойства визуальных компонентов, которые приводят к изменению внешнего вида программы.

 

 

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

Изучение класса TThread начнем с метода Execute:

procedure Execute; virtual; abstract;

Это и есть код, исполняемый в создаваемом потоке TThread.

Примечание

Хотя формальное описание Execute — метод abstract, но мастер создания нового объекта TThread создает пустой шаблон этого метода.

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

Если поток был создан с аргументом

CreateSuspended: = False, то метод Execute выполняется немедленно,

в противном случае Execute выполняется после вызова метода Resume.

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

Если же в потоке будет выполняться какой-то цикл, и поток должен завершиться вместе с приложением, то условия окончания цикла должны быть примерно такими:

procedure TMyThread.Execute;

begin

Repeat

DoSomething;

Until CancelCondition or Terminated;

end;

Здесь CancelCondition — ваше личное условие завершения потока (исчерпание данных, окончание вычислений, поступление на вход того или иного символа и т. д.),

а свойство Terminated сообщает о завершении потока (это свойство может быть установлено как изнутри потока, так и извне; чаще всего, завершается породивший его процесс).

Конструктор объекта:

constructor Create (CreateSuspended: Boolean);

получает параметр CreateSuspended. Если его значение равно True, вновь созданный поток не начинает выполняться до тех пор, пока не будет сделан вызов метода Resume.

В случае если параметр CreateSuspended имеет значение False, конструктор завершается и только затем поток начинает исполнение.

destructor Destroy; override;

Деструктор Destroy вызывается, когда необходимость в созданном потоке отпадает. Деструктор завершает его и высвобождает все ресурсы, связанные с объектом TThread.

function Terminate: Integer;

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

property Terminated: Boolean;

в значение True. Таким образом, Terminate — это указание потоку завершиться, выраженное " в мягкой форме", с возможностью корректно освободить ресурсы. Если нужно немедленно завершить поток, лучше использовать функцию Windows API TerminateThread.

Примечание

Метод Terminate автоматически вызывается и из деструктора объекта. Поток — объект VCL будет дожидаться, пока завершится поток — объект операционной системы. Таким образом, если поток не умеет завершаться корректно, вызов деструктора потенциально может привести к зависанию всей программы.

Еще одно полезное свойство:

property FreeOnTerminate: Boolean;

Если это свойство равно True, то деструктор потока будет вызван автоматически по его завершении. Это очень удобно для тех случаев, когда нет уверенности в том, когда именно завершится поток, и необходимо использовать его по принципу " выстрелил и забыл" (fire and forget).

function WaitFor: Integer;

Метод WaitFor предназначен для синхронизации и позволяет одному потоку дождаться момента, когда завершится другой поток. Если вы внутри потока FirstThread пишите код

Code: = SecondThread.WaitFor;

то это означает, что поток FirstThread останавливается до момента завершения потока SecondThread. Метод WaitFor возвращает код завершения ожидаемого потока (см. свойство Returnvalue).

property Handle: THandle read FHandle;

property ThreadID: THandle read FThreadID;

Свойства Handle и ThreadID дают программисту непосредственный доступ к потоку средствами API Win32. Если разработчик хочет обратиться к потоку и управлять им, минуя возможности класса TThread, значения Handle и ThreadID могут быть использованы в качестве аргументов функций API Win32.

Например, если программист хочет перед продолжением выполнения приложения дождаться завершения сразу нескольких потоков, он должен вызвать функцию API WaitForMultipieObjects; для ее вызова необходим массив дескрипторов потоков.

property Priority: TThreadPriority;

Свойство Priority позволяет запросить и установить приоритет потоков. Допустимыми значениями приоритета для объектов TThread являются tpIdle, tpLowest, tpLower, tpNormai, tpHigher, tpHighest и tpTimeCritical.

procedure Synchronize (Method: TThreadMethod);

Этот метод относится к секции protected, т. е. может быть вызван только из потомков TThread.

Delphi предоставляет программисту метод Synchronize для безопасного вызова методов VCL внутри потоков.

Во избежание конфликтных ситуаций, метод Synchronize дает гарантию, что к каждому объекту VCL одновременно имеет доступ только один поток.

Аргумент, передаваемый в метод Synchronize, — это имя метода, который производит обращение к VCL; вызов Synchronize с этим параметром — это тоже, что и вызов самого метода. Такой метод (класса TThreadMethod) не должен иметь никаких параметров и не должен возвращать никаких значений. К примеру, в основной форме приложения нужно предусмотреть функцию

procedure TMainForm.SyncShowMessage;

begin

ShowMessage (IntToStr (ThreadList1.Count)); // другие обращения к VCL

end;

а в потоке для показа сообщения писать не

ShowMessage (IntToStr(ThreadListl.Count));

и даже не

MainForm.SyncShowMessage;

а только так:

Synchronize (MainForm.SyncShowMessage);

Примечание

Производя любое обращение к объекту VCL из потока, убедитесь, что при этом используется метод Synchronize; в противном случае результаты могут оказаться непредсказуемыми. Это верно даже в том случае, если вы используете средства синхронизации, описанные ниже.

procedure Resume;

Метод Resume класса TThread вызывается, когда поток возобновляет выполнение после остановки, или для явного запуска потока, созданного с параметром CreateSuspended, равным True.

procedure Suspend;

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

property Suspended: Boolean;

Свойство Suspended позволяет программисту определить, не приостановлен ли поток. С помощью этого свойства можно также запускать и останавливать поток.

Установив свойство Suspended в значение True, вы получите тот же результат, что и при вызове метода Suspend — приостановку. Наоборот, установка свойства Suspended в значение False возобновляет выполнение потока, как и вызов метода Resume.

property ReturnValue: Integer;

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

 

 

<== предыдущая лекция | следующая лекция ==>
У якому рядку вжито речення з порушенням синтаксичних норм? | При­ме­ча­ние. Плот­ность бе­то­на равна 2300 кг/м3.




© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.