prosdo.ru
добавить свой файл
1
Потоки

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

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

Определение потока довольно простое: потоки — это объекты, получающие время процессора. Время процессора выделяется квантами (quantum, time slice). Квант времени — это интервал, имеющийся в распоряжении потока до тех пор, пока время не будет передано в распоряжение другого потока.
Теория потоков.

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

Какой код нужно помещать в отдельный поток? Вот некоторые пример:

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

2. Если какие-то расчёты идут достаточно долго, то многие считают, что их тоже нужно помещать в поток. Просто когда идут такие расчеты, программа блокируется и невозможно нажать кнопку «Отмена» или что-нибудь подобное. Это неправильное утверждение. Поток тут абсолютно необязателен, потому что можно обойтись и без него. Достаточно внутри расчётов поставить вызов Application.ProcessMessages и в этом месте выполнение расчётов будет прерываться на некоторое время, и программа будет обслуживать другие сообщения, пришедшие от пользователя. Таким образом получиться простой эффект многозадачности без использования потока.


3. Код критичен к времени выполнения. Допустим, что ваша программа должна принимать какие-то данные по COM порту. Как только на порт пришли какие-то данные, они должны быть моментально обработаны с минимальной задержкой. Вот такие вещи желательно выносить в отдельный поток, потому что если в момент поступления данных программа занята большими расчётами, то данные могут оказаться необработанными.

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

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

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

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

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


  • Real time;

  • Normal;

  • High;

  • Below normal;

  • Above normal;

  • Idle.

Приоритеты имеют значения от 0 до 31. Процесс, породивший поток, может впоследствии изменить его приоритет; в этой ситуации программист имеет возможность управлять скоростью отклика каждого потока.
Класс TThread
Объект класса TThread — это конструкция Delphi, соответствующая потоку ОС. Изучение класса TThread начнем с метода Execute:

procedure Execute; virtual; abstract; Это и есть код, исполняемый в создаваемом вами потоке TThread. Если поток был создан с аргументом CreateSuspended, равным False, то метод Execute выполняется немедленно, в противном случае Execute выполняется после вызова метода Resume. Свойство Terminated сообщает о завершении потока (это свойство может быть установлено как изнутри потока, так и извне;

Конструктор объекта: constructor Create(CreateSuspended: Boolean); получает параметр CreateSuspended. Если его значение равно True, вновь созданный поток не начинает выполняться до тех пор, пока не будет сделан вызов метода Resume. В случае если параметр CreateSuspended имеет значение False, конструктор завершается и только затем поток начинает исполнение.

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

function Terminate: Integer; Для окончательного завершения потока (без последующего запуска) существует метод Terminate. Устанавливает свойство property Terminated: Boolean; в значение True. Таким образом, Terminate — это указание потоку завершиться, выраженное "в мягкой форме", с возможностью корректно освободить ресурсы.


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 могут быть использованы в качестве аргументов функций Win32 API.

property Priority: TThreadPriority;

Свойство Priority позволяет запросить и установить приоритет потоков. Допустимыми значениями приоритета для объектов TThread являются:

tpIdle - поток будет работать только когда процессор бездельничает.

tpLowest - самый слабый приоритет

tpLower - слабый приоритет

tpNormal - нормальный

tpHigher - высокий

tpHighest - самый высокий

tpTimeCritical - критичный.

procedure Synchronize(Method: TThreadMethod); Этот метод относится к секции protected, т. е. может быть вызван только из потомков TThread.

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 нужно вернуть число не прошедших орфографическую проверку слов.
Процесс. Порождение дочернего процесса
Процессы порождаются запуском новых приложений. В некоторых случаях требуется из работающего приложения запустить на выполнение другое приложение. Например, открыть калькулятор, вызвать программу-архиватор и др. Запуск одной программы из другой называют «порождением дочернего процесса». Запуск внешней программы из приложения Delphi можно выполнить несколькими способами, предусматривающими использование функций API Windows: WinExec, ShellExecute и CreateProcess.

Проще всего воспользоваться функцией WinExec, которая работает в разных версиях Windows и может выполнять как приложения Windows, так и программы MS DOS:

function WinExec (CmdLine: PChar; CmdShow: integer): integer; Параметр CmdLine является указателем на строку, содержащую имя исполняемого файла и при необходимости – дополнительные сведения (параметры командной строки). Если имя файла указано без пути, то Windows будет искать его, последовательно просматривая каталоги:


1) тот, из которого было запущено приложение;

2) текущий каталог;

3) системный каталог Windows;

4) главный каталог Windows;

5) каталоги, заданные командой PATH (переменной окружения).

Параметр CmdShow определяет способ отображения окна запускаемого приложения. Для приложений Windows этот параметр может принимать большое количество значений. Наиболее часто используются значения SW_RESTORE и SW_SHOWNORMAL, которые активизируют и отображают окно в обычном виде. После запуска новое приложение выполняется вне зависимости от работы вызвавшего его приложения. Функция WinExec возвращает целое число, которое при удачном запуске больше 32. При несостоявшемся запуске сообщений на экран не выводится, функция WinExec возвращается код ошибки (от 0 до 32), который можно обработать программно. Например, для запуска Калькулятора можно записать: WinExec(PChar('calc.exe'), SW_ShowNormal); При необходимости можно обеспечить выбор запускаемого приложения пользователем во время работы. В приведённой ниже процедуре обработки щелчка по кнопке Button3 используется стандартное окно диалога OpenDialog1 для задания нужного файла.

procedure TForm1.Button3Click(Sender: TObject);

var cod:byte;

begin

if OpenDialog1.Execute then

begin

cod:= WinExec(PChar(OpenDialog1.FileName), SW_Restore);

if cod=0 then ShowMessage('Не хватает ресурсов');

end;

end;

Функция ShellExecute обладает большими возможностями, она позволяет не только запускать на выполнение заданное приложение, но и открывать документы и печатать их. Например, обычно с документами, имеющими расширение .doc, связан редактор Word, а с документами, имеющими расширение .xls – табличный процессор Excel. Чтобы начать работу с документом, надо запустить соответствующую программу (в данном случае Word или Excel) и открыть нужный файл. Кроме того, функция ShellExecute позволяет открыть указанную папку, то есть запустить программу Проводник с открытой требуемой папкой. Чтобы воспользоваться функцией ShellExecute, необходимо в раздел uses добавить модуль ShellAPI. При вызове функции ShellExecute необходимо указать достаточно много сведений:


function ShellExecute (Wnd:HWnd; Operation, FileName, Parameters, Directory: PChar; CmdShow: integer): THandle; Параметр Wnd содержит ссылку на окно, из которого запускается новое приложение. Обычно для задания «родительского» окна используют Handle. Параметр FileName является указателем на строку, содержащую имя файла или каталога. Параметр Operation указывает на строку, задающую выполняемую операцию. Определено три значения: open (открыть файл или каталог), print (напечатать документ), explore (открыть папку). Если этот параметр равен nil, то выполняется команда open. Параметр Parameters указывает на строку, содержащую передаваемые запускаемому приложению опции. Если же FileName задаёт неисполняемый файл, то значением этого параметра должно быть nil. Параметр Directory указывает на строку с именем каталога по умолчанию. Если имя каталога не задано, то значением этого параметра должно быть nil. Параметр CmdShow определяет способ отображения окна запускаемого приложения, может принимать те же значения, что и параметр CmdShow функции WinExec. В качестве результата функция ShellExecute возвращает ссылку на запущенное приложение, значение которой при успешном выполнении больше 32. При неудачном выполнении возвращается код ошибки. Значения кодов ошибки те же, что и для функции WinExec.

Примеры использования функции ShellExecute:

ShellExecute(Handle,'open','calc.exe',nil,nil,SW_ShowNormal);

ShellExecute(Handle,nil,'текст.doc',nil,nil,SW_ShowNormal);

if OpenDialog1.Execute then ShellExecute(Handle,'open',PChar(OpenDialog1.FileName), nil,nil,SW_Restore);

Функция CreateProcess

function CreateProcess(lpApplicationName: PChar; lpCommandLine: PChar; lpProcessAttributes, lpThreadAttributes: PSecurityAttributes; blnheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer; lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo; var lpProcessinformation: TProcessInformation): BOOL;


Первые два параметра — это имя запускаемого приложения и передаваемые ему в командной строке параметры. Параметр dwCreationFlags содержит флаги, определяющие способ создания нового процесса и его будущий приоритет. Использованные в приведенном ниже листинге флаги означают: CREATE_NEW_CONSOLE — будет запущено новое консольное приложение с отдельным окном; NORMAL_PRIORITY_CLASS — нормальный приоритет. Структура TStartupInfo содержит сведения о размере, цвете, положении окна создаваемого приложения. В нижеследующем примере используется поле wshowwindow: установлен флаг SW_SHOWNORMAL, означающий визуализацию окна с нормальным размером. На выходе функции заполняется структура lpProcessinformation. В ней программисту возвращаются дескрипторы и идентификаторы созданного процесса и его первичного потока. Нам понадобится дескриптор процесса — в нашем примере создается консольное приложение, затем происходит ожидание его завершения. "Просигналит" нам об этом именно объект lpProcessinformation.hProcess.

Пример:

var

lpStartupInfo: TStartupInfo;

lpProcessinformation: TProcessInformation;

begin

FillChar(lpStartupInfo,Sizeof(lpStartupInfo) ,#0) ;

lpStartupInfo.cb := Sizeof(lpStartupInfo) ;

lpStartupInfo.dwFlags := STARTF_USESHOWWINDOW;

lpStartupInfo.wShowWindow := SW_SHOWNORMAL;

if not CreateProcess(nil, PChar('ping localhost'), nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, lpStartupInfo, lpProcessInformation) then

ShowMessage(SysErrorMessage(GetLastError)) else

begin

WaitForSingleObject(lpProcessInformation.hProcess, 10000);

CloseHandle(lpProcessInformation.hProcess);

end;

end;
ВОПРОСЫ ДЛЯ САМОКОНТРОЛЯ



  1. Опишите понятие потока.

  2. Что представляет собой приоритет потока, перечислите классы приоритетов.

  3. Опишите класс TThread и его основные методы и свойства.


  4. Что подразумевается под синхронизацией?

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

  6. Можно ли из приложения Delphi запустить документ Word (Excel)?

  7. Запишите фрагмент кода, предназначенный для запуска файла .xls и обеспечивающий обработку исключительных ситуаций.

  8. Какие приложения называют консольными?

  9. В каких случаях целесообразно использовать консольные приложения?

  10. Как организован ввод и вывод данных в консольных приложениях?

  11. Как программно передать фокус ввода нужному компоненту?

  12. Как задать нужную очерёдность получения компонентами фокуса ввода?

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

  14. Можно ли из приложения Delphi запустить документ Word (Excel)?

  15. Запишите оператор, позволяющий вывести на экран html-документ.

  16. Запишите фрагмент кода, предназначенный для запуска файла .xls и обеспечивающий обработку исключительных ситуаций.