Нить в своїх додатках. Частина 2., Різне, Програмування, статті

Джерело: webdelphi

У попередній статті, в прикладі, я не став описувати принцип роботи з методом Synchronize, І як я тепер розумію марно. Звичайно, я виклав архів з прикладом, де цей метод зустрічається в кількох місцях, і звичайно ці моменти були мною прокоментовані. Проте є кілька нюансів, стосуються синхронізації, та й не тільки її, про які мені все ж слід було згадати в статті, а не в кількох рядках коду. У цій статті я спробую заповнити цю прогалину.

Синхронізація


Перш ніж додавати в будь-який потік методи синхронізації, потрібно чітко визначитися, на яких етапах роботи потоку і з якою метою це необхідно. І попередньо підготувати самі методи синхронізації. Повернемося до нашого прикладу компонент TDownloader, Його основне завдання – скачування одиночного файлу з інтернету по заданому URL. Всі базові процедури для роботи з інтернетом працюють за простим принципом – “Відправили запит, отримали відповідь, повернули результат, або помилку в разі невдачі”. Можна було б зупинитися на такому ж принципі, і після обробки запиту по завантаженню файлу просто повертати результат.


Однак процес скачування файлів справа тривалий, а значить банальна напис “Почекайте качаю” нас навряд чи влаштує. Хотілося б так сказати небудь інформації, про те, на якому конкретно етапі знаходиться стрибка. Сам по собі потік працює не помітно, і на роботі програми його праці ні як не відображаються. А значить, потрібна якась система “оповіщення” про поточний стан процесу скачки. Тут-то нам і стане в нагоді чудовий метод Synchronize. Дивимося уважно на наш алгоритм отримання файла з інтернету, і ділимо його на етапи, за якими потоку необхідно звітувати перед програмою, щоб та реагувала сама, і давала необхідну інформацію користувачеві.


Найпершою рядком, потік відкриває сесію функцією InternetOpen, Яка у разі успішного виконання повертає ідентифікатор сесії, або nil у разі невдачі. Далі аналогічна функціяInternetOpenUrl. На даний момент немає сенсу звітувати по кожній успішно проведеної операції, часом подібна інформативність може виявитися навіть зайвою. А ось про помилки потік повідомити зобов’язаний. Причому, як користувачу, так і програмі важливий не тільки факт помилки, а й причини по яких не може бути завершена виконувана операція. А значить, нам потрібен метод синхронізації повідомляє про помилку і про причини її виникнення.


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

TDownloadError = (deInternetOpen, deInternetOpenUrl);

Далі оголошуємо змінну і сам метод в секції private:

err: TDownloadError;
procedure toError;

Що конкретно повинен робити цей метод? Можна було б відразу виводити повідомлення користувачу, або записати повідомлення про помилку в лог-файл. Але ми пишемо не кінцеву програму, а компонент, а значить, що робити з помилкою вирішувати програмі. А значить нам потрібно оголосити подія OnError.

Type
{………………….}
TErrorEvent = procedure(Sender: TObject; E: TDownloadError) of object;
{………………….}
private
fOnError: TErrorEvent;
{………………….}
public
property OnError: TErrorEvent read fError write fError;

Тепер нам нічого не варто дописати метод toError:

procedure TDownloadThread.toError;
begin
if Assigned(fError) then
OnError(Self, err);
end;

Вносимо зміни в метод Execute:

{……………………..}
pInet := InternetOpen(“Dowload Master”, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); if pInet = nil then / / якщо функція повернула помилку
begin err: = deInternetOpen; / / ложим повідомлення про помилку в змінну Synchronize (toError); / / синхронізуємо потоки Exit; / / завершуємо потік
end;
try
pUrl := InternetOpenUrl(pInet, PChar(URL), nil, 0, INTERNET_FLAG_PRAGMA_NOCACHE or INTERNET_FLAG_RELOAD, 0); if pUrl = nil then / / якщо функція повернула помилку
begin err: = deInternetOpenUrl; / / ложим повідомлення про помилку в змінну Synchronize (toError); / / синхронізуємо потоки Exit; / / завершуємо потік
end;

Як бачите нічого складного. Кода, звичайно, стало набагато більше, але це легко устранімим недолік. Як скоротити даний запис синхронізації до однієї простої рядки ви можете подивитися в архіві, доданому до статті. А поки рухаємося далі по методу Execute, І бачимо простий цикл скачування файлу по частинах.

{………………………..}
repeat
if Terminated then
Break;
FillChar(Buffer, SizeOf(Buffer), 0);
if InternetReadFile(pUrl, @Buffer, Length(Buffer), BytesRead) then
MemoryStream.Write(Buffer, BytesRead)
until (BytesRead = 0);
{………………………..}

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

  if InternetReadFile(pUrl, @Buffer, Length(Buffer), BytesRead) then
MemoryStream.Write(Buffer, BytesRead)
else begin / / якщо функція повернула помилку err: = deDownloadingFile; / / ложим повідомлення про помилку в змінну Synchronize (toError); / / синхронізуємо потоки Exit; / / Завершуємо потік
end;

І не забудемо додати нове значення помилки в наш “помилковий” тип:

TDownloadError = (deInternetOpen, deInternetOpenUrl, deDownloadingFile);

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

Type
{…………………………..}
TDownloadingEvent = procedure(Sender: TObject; AccepteSize: Cardinal) of object;
{…………………………..}
private
AccepteSize: Cardinal;
fDownloading: TDownloadingEvent;
procedure toDownloading;
{…………………………..}
public
property OnDownloading: TDownloadingEvent read fDownloading write fDownloading;
{…………………………..}
procedure TDownloadThread.toDownloading;
begin
if Assigned(fDownloading) then
fDownloading(Self, AccepteSize);
end;
{…………………………..}

І вносимо зміни в Execute, Тут нам досить дописати всього один рядок:

  if InternetReadFile(pUrl, @Buffer, Length(Buffer), BytesRead) then
MemoryStream.Write(Buffer, BytesRead)
else
begin
err := deDownloadingFile;
Synchronize(toError);
Exit;
end; Synchronize (toDownloading); / / синхронізуємо методом toDownloading

Залишилося тільки додати подія на закінчення закачування. А варіантів закінчень у нас може бути цілих 2. У першому випадку скачування завершується, із закінченням файлу, а в другому за бажанням користувача (або програми), адже ми передбачили можливість переривання циклу закачування.

{……………………………..}
repeat
if Terminated then
Break;
{……………………………..}

А значить нам потрібно ще два нові події. Для них можна не створювати окремий тип, оскільки вони не мають на увазі ніяких параметрів. Скористаємося стандартним TNotifyEvent.

{……………………………..}
private
fAccepted: TNotifyEvent;
fBreak: TNotifyEvent;
procedure toAccepted;
procedure toBreak;
{……………………………..}
public
property OnAccepted: TNotifyEvent read fAccepted write fAccepted;
property OnBreak: TNotifyEvent read fBreak write fBreak;
{……………………………..}
procedure TDownloadThread.toAccepted;
begin
if Assigned(fAccepted) then
fAccepted(Self);
end;
procedure TDownloadThread.toBreak;
begin
if Assigned(fBreak) then
fBreak(Self);
end;
{……………………………..}

Завершуємо Execut:

{……………………………..}
if Terminated then
Synchronize(toBreak)
else
Synchronize(toAccepted);
end;
{……………………………..}

Тепер наш потік навчився звітувати у своїх діях. Однак використовувати його в програмі все ще незручно. Так як клас TThread, Не є компонентом (в модулі Classes його оголошено як “TThread = class“), Ми не зможемо додати його нащадка в палітру. А значить, все обробники подій нам доведеться прописувати вручну, кожен раз при його використанні. Щоб усунути цей недолік напишемо клас” посередник “, який зробимо нащадком TComponent.


Компонент посередник


У цьому компоненті потрібно продублювати всі оголошені події, а також для зручності додати кілька властивостей і методів.
Додамо дві властивості: “Url: string“- Для завдання адреси, і”OutStream: TMemoryStream“Для доступу до результату. Два методу Download і BreakDownload, Для ініціалізації і переривання скачування відповідно. І як я вже говорив вище, продублюємо всі події TDownloadThread.

{………………………………..}
TDownloader = class(TComponent)
private
Downloader: TDownloadThread;
fOutStream: TMemoryStream;
fURL: string;
fOnError: TErrorEvent;
fOnAccepted: TNotifyEvent;
fOnBreak: TNotifyEvent;
fOnDownloading: TDownloadingEvent;
public
procedure Download;
procedure BreakDownload;
property OutStream: TMemoryStream read fOutStream;
published
property URL: string read fURL write fURL;
property OnError: TErrorEvent read fOnError write fOnError;
property OnAccepted: TNotifyEvent read fOnAccepted write fOnAccepted;
property OnBreak: TNotifyEvent read fOnBreak write fOnBreak;
property OnDownloading: TDownloadingEvent read fDownloading write fDownloading;
end;
{………………………………..}
procedure TDownloader.Download;
begin
if Assigned(Downloader) then
Downloader.Terminate;
if Assigned(fOutStream) then
FreeAndNil(fOutStream);
fOutStream := TMemoryStream.Create;
Downloader := TDownloadThread.Create(True, URL, Pointer(fOutStream));
Downloader.OnError := OnError;
Downloader.OnAccepted := OnAccepted;
Downloader.OnBreak := OnBreak;
Downloader.OnDownloading := OnDownloading;
Downloader.Resume;
end;
procedure TDownloader.BreakDownload;
begin
if Assigned(Downloader) then
Downloader.Terminate;
end;
{………………………………..}

До речі, нагадаю, що конструктор у TDownloadThread оголошений як:

constructor Create(CreateSuspended: Boolean; const URL: String; Stream: PMemoryStream);

Де CreateSuspended у разі якщо воно дорівнює True, Не дає запуститися потоку до виконання методу Resume. Чим ми і користуємося для завдання обробників подій. Після чого запускаємо сам потік. А в процедурі BreakDownload, І зовсім все просто, вона всього лише повідомляє потоку методом Terminate, Що йому необхідно завершити свою роботу.


Додам, що метод Synchronize призупиняє роботу потоку, проте з Delphi 2009, З’явився новий метод для синхронізації Queue, Який не чекаючи закінчення синхронізації відновлює роботу потоку. Така необхідність, як правило, виникає не часто, але коли вона виникає, цей метод допомагає позбутися від не потрібного простою потоку.
На цьому мабуть все. В архіві ви знайдете доопрацьований приклад, в якому також реалізований запит розміру викачуваного файлу.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*