Смекни!
smekni.com

Робота з "потоками" в середовищі Delphi (стр. 4 из 4)

Процес. Породження дочірнього процесу

Об'єкт типу процес (process) може бути використаний для того, щоб припинити виконання потоку в тому випадку, якщо він для свого продовження потребує завершення процесу. З практичної точки зору така проблема встає, коли потрібно в рамках вашого додатку виконати додаток, створений кимось іншим, або, наприклад, сеанс MS-DOS.

Розглянемо, як, власне, один процес може породити інший. Замість застарілої і підтримуваної тільки для сумісності функції winExec, що перекочувала з колишніх версій Windows, набагато правильніше використовувати могутнішу:

function CreateProcess (IpApplicationName: PChar; IpCorranandLine: PChar;

IpProcessAttributes, IpThreadAttributes: PSecurityAttributes;

blnheritHandles: BOOL;

dwCreationFlags: DWORD; IpEnvironment: Pointer;

IpCurrentDirectory: PChar;

const IpStartupInfo: TStartupInfo;

var IpProcessInformation: TProcessInformation): BOOL;

Перші два параметри ясні – це ім'я додатку, що запускається, і передавані йому в командному рядку параметри. Параметр dwCreationFlags містить прапори, що визначають спосіб створення нового процесу і його майбутній пріоритет. Використані в приведеному нижче лістингу прапори означають: CREATE_NEW_CONSOLE – будет запущено новий консольний додаток з окремим вікном; NORMAL_PRIORITY_CLASS – нормальний пріоритет.

Структура TStartupInfo містить відомості про розмір, колір, положення вікна створюваного додатку. У нижченаведеному прикладі (лістинг 29.1) використовується поле wshowwindow: встановлений прапор SW_SHOWNORMAL, що означає візуалізацію вікна з нормальним розміром.

На виході функції заповнюється структура IpProcessInformation. У ній програмісту повертаються дескриптори і ідентифікатори створеного процесу і його первинного потоку. Нам знадобиться дескриптор процесу – в нашому прикладі створюється консольний додаток, потім відбувається очікування його завершення. «Просигналить» нам про це саме об'єкт IpProcessInformation.hProcess.

Лістинг 29.1. Породження дочірнього процесу

var

IpStartupInfo: TStartupInfo;

IpProcessInformation: TProcessInformation;

begin

FillChar (IpStartupInfo, Sizeof(IpStartupInfo), 10);

IpStartupInfo.cb:= Sizeof(IpStartupInfo);

IpStartupInfo.dwFlags:= STARTFJJSESHOWWINDOW; IpStartupInfo.wShowWindow:= SW_SHOWNORMAL;

if not CreateProcess (nil,

PChar ('ping localhost'),

nil,

nil,

false,

CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,

nil,

nil,

IpStartupInfo, IpProcessInformation) then

ShowMessage (SysErrorMessage(GetLastError;)

else

begin

WaitForSingleObject

(IpProcessInformation.hProcess, 10000); CloseHandle (IpProcessInformation.hProcess);

end;

end;

Потік

Потік може чекати інший потік точно так, як і інший процес. Очікування можна організувати за допомогою функцій API (як в тільки що розглянутому прикладі), але зручніше це зробити за допомогою методу TThread. WaitFor.

Консольне введення

Консольне введення (console input) годиться для потоків, які повинні чекати відгуку на натиснення користувачем клавіші на клавіатурі. Цей тип очікування може бути використаний в програмі дуплексного зв'язку (chat). Один потік при цьому чекатиме отримання символів; другий – відстежувати введення користувача і потім посилати набраний текст чекаючому додатку.

Сповіщення про зміну у файловій системі

Цей вид об'єкту очікування дуже цікавий і незаслужено мало відомий. Ми розглянули практично всі варіанти того, як один потік може подати сигнал іншому. А як одержати сигнал від операційної системи? Ну, наприклад, про те, що у файловій системі відбулися якісь зміни? Такий вид сповіщення з ОС UNIX і доступний програмістам, що працюють з Win32. Для організації моніторингу файлової системи потрібно використовувати

Три функції – FindFirstChangeNotification, FindNextChangeNotification і FinddoseChangeNotification. Перша з них повертає дескриптор об'єкту файлового сповіщення, який можна передати у функцію очікування. Об'єкт активізується тоді, коли в заданій теці відбулися ті або інші зміни (створення або знищення файлу або теки, зміна прав доступу і т. д.). Друга – готує об'єкт до реакції на наступну зміну. Нарешті, за допомогою третьої функції слід закрити той, що став непотрібним об'єкт.

Так може виглядати код методу Execute потоку, створеного для моніторингу файлової системи:

var DirName: string;

procedure TSimpleThread. Execute;

var r: Cardinal;

fn: THandle;

begin

fn:= FindFirstChangeNotification (pChar(DirName), True,

FILEJTOTIFY_CHANGE_FILE_NAME);

repeat

r:= WaitForSingleObject (fn, 2000);

if r = WAIT_OBOECT_0 then

Synchronize (Forml. UpdateList);

if not FindNextChangeNotification(fn) then

break;

until Terminated;

FindCloseChangeNotification(fn);

end;

На головній формі повинні знаходитися компоненти, потрібні для вибору обстежуваної теки, а також компонент TListBox, в який записуватимуться імена файлів:

procedure TForml. ButtonlClick (Sender: TObject);

var dir: string; begin

if SelectDirectory (dir, [], 0)

then begin

Editl. Text:= dir; DirName:= dir;

end;

end;

procedure TForml. UpdateList;

var SearchRec: TSearchRec;

begin

ListBoxl. Clear;

FindFirst (Editl. Text+'\*.*', faAnyFile, SearchRec); repeat ListBoxl. Items. Add (SearchRec. Name);

until FindNext(SearchRec)<> 0;

FindClose(SearchRec);

end;

Додаток готовий. Щоб воно стало повнофункціональним, передбачте в ньому механізм перезапуску потоку при зміні обстежуваної теки.

Локальні дані потоку

Цікава проблема виникає, якщо в додатку буде декілька однакових потоків. Як уникнути сумісного використовування одних і тих же змінних декількома потоками? Перше, що спадає на думку, – додати і використати поля об'єкту – нащадка TThread, які можна додати при його створенні. Кожен потік відповідає окремому екземпляру об'єкту, і їх дані перетинатися не будуть. (До речі, це одна з великих зручностей використовування класу TThread.) Але є функції API, які знать не знають про об'єкти Delphi і їх поля і властивості. Для підтримки розділення даних між потоками на нижньому рівні в мову Object Pascal введена спеціальна директива – threadvar, яка відрізняється від директиви опису змінних var тим, що застосовується тільки до локальних даних потоку. Наступний опис:

Var

datal: Integer; threadvar

data2: Integer;

означає, що змінна datal використовуватиметься всіма потоками даного додатку, а змінна data2 буде у кожного потоку своя.

Як уникнути одночасного запуску двох копій одного додатку

Така задача виникає дуже часто. Багато, що особливо починають, користувачів не цілком розуміють, що між клацанням по значку додатку і його запуском може пройти декілька секунд, а то і десятків секунд. Вони починають клацати по значку, запускаючи всі нові копії. Тим часом, при роботі з базами даних і в багатьох інших випадках мати більше однієї копії не тільки не потрібно, але і шкідливо.

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

Приклад такого ресурсу – загальний блок у файлі, що відображається в пам'ять. Оскільки цей ресурс має ім'я, можна зробити його унікальним саме для вашого додатку:

var UniqueMapping: THandle;

FirstWindow: THandle

; begin

UniqueMapping:= CreateFileMapping ($ffffffff,

nil, PAGE_READONLY, 0, 32,'MyMap');

if UniqueMapping = 0 then

begin

ShowMessage (SysErrorMessage(GetLastError));

Halt;

end

else if GetLastError = ERROR_ALREADY_EXISTS then

begin

FirstWindow:= FindWindowEx (0, 0, TfmMain. ClassName, nil);

if FirstWindowoO then

SetForegroundWindow(FirstWindow);

Halt;

end;

// Немає інших копій – продовження Application. Initialize;

Приблизно такі рядки потрібно вставити в початок тексту проекту до створення форм. Блок спільно використовуваної пам'яті виділяється в системному сторінковому файлі (про це говорить перший параметр, рівний -1, див. опис функції CreateFileMapping). Його ім'я – муМар. Якщо при створенні блоку буде одержаний код помилки ERROR_ALREADY__EXISTS, це свідчить про наявність працюючої копії додатку. В цьому випадку додаток перемикає фокус на головну форму іншого екземпляра і завершується; інакше процес ініціалізації продовжується.

Потоки, як і інші могутні інструменти, повинні бути використані з обережністю і без зловживань, оскільки можуть виникнути помилки, які дуже важко знайти. Є дуже багато доводів за використовування потоків, але є і доводи проти цього. Робота з потоками буде простішим, якщо враховувати нижче приведені положення.

· Якщо потоки працюють тільки із змінними, оголошеними усередині їх власного класу, то ситуації гонок і безвиході украй маловірогідні.

Іншими словами, уникайте використовування в потоках глобальних змінних і змінних інших об'єктів.

· Якщо ви звертаєтеся до полів або методів об'єктів VCL, робіть це тільки за допомогою методу Synchronize.

· Не «пересинхронізіруйте» ваш додаток, а не те воно працюватиме як один єдиний потік. Надмірно синхронізований додаток втрачає всі переваги від наявності декількох потоків, оскільки вони постійно зупинятимуться і чекатимуть синхронізації. Потоки надають витончене рішення деяких сьогоднішніх проблем програмування; але вони також ускладнюють і без того непростий процес відладки. Та все ж переваги потоків однозначно переважують їх недоліки.