(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Многопоточность в своих приложениях. Часть 2.

Источник: webdelphi

Источник: 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, который не дожидаясь окончания синхронизации возобновляет работу потока. Такая необходимость, как правило, возникает не часто, но когда она возникает, этот метод помогает избавиться от не нужного простоя потока.
На этом пожалуй все. В архиве вы найдете доработанный пример, в котором также реализован запрос размера скачиваемого файла.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 27.01.2010 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Enterprise Connectors (1 Year term)
Delphi Professional Named User
Panda Global Protection - ESD версия - на 1 устройство - (лицензия на 1 год)
Quest Software. TOAD for SQL Server Xpert Edition
Traffic Inspector GOLD 5 Учетных записей
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Компьютерный дизайн - Все графические редакторы
СУБД Oracle "с нуля"
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100