Практика OTL. Фоновый поиск файлов.
В этом разделе мы немного отвлечемся от исследования возможностей OTL. Перейдем так сказать к практике. И начнем рассмотрение с демонстрационного примера tests\23_BackgroundFileSearch показывающего как с помощью OTL можно сделать фоновый поиск файлов.
Откроем данный пример в IDE.Как вы видите, у нашего приложения будет простой интерфейс:
В строке ввода мы будем вводить путь и маску поиска, после чего нажав кнопку “Scan” приложение начнет просматривать заданную папку на наличие файлов заданных в маске и в поле “Found” отображать количество найденных файлов в поле.
После того как процесс сканирование закончится в списке появятся найденные файлы.
Во время сканирования, основной (GUI) поток программы будет доступен, вы спокойно сможете перемещать, сворачивать, максимизировать окно программы.
Стоит также отметить, что помимо компонентов, которые видны на скриншоте, на форме присутствуют также компоненты TOmniEventMonitor (назван OTLMonitor) и TTimer (tmrDisplayStatus).
Когда пользователь щелкает кнопкой Scan, то создается фоновая задача и она запускается на выполнение.
test_23_BackgroundFileSearch.pas Запуск фоновой задачи
procedure TfrmBackgroundFileSearchDemo.btnScanClick(Sender: TObject);
begin
FFileList := TStringList.Create;
btnScan.Enabled := false;
tmrDisplayStatus.Enabled := true;
FScanTask := CreateTask(ScanFolders, 'ScanFolders')
.MonitorWith(OTLMonitor)
.SetParameter('FolderMask', inpFolderMask.Text)
.Run;
end;
Рабочий метод фоновой задачи ScanFolders, который выполняется во второстепенном потоке. Мы возвратимся к этому методу позже. Задача будет запущена с мониторингом компонента OTLMonitor. Это сделано так, чтобы мы имели возможность получать сообщения с фоновой задачи. OTLMonitor также проинформирует нас об завершении задачи. Входная папка и маска поиска, посылаются в фоновую задачу как параметр FolderMask. Переменная FFileList типа TStringList будет содержать список всех найденных файлов.
Когда задача закончит свою работу, то будет вызван обработчик OTLMonitor.OnTaskTerminated:
test_23_BackgroundFileSearch.pas Обработчик окончания фоновой (завершения) задачи
procedure TfrmBackgroundFileSearchDemo.OTLMonitorTaskTerminated(
const task: IOmniTaskControl);
begin
tmrDisplayStatus.Enabled := false;
outScanning.Text := '';
outFiles.Text := IntToStr(FFileList.Count); // кол-во найденных файлов
lbFiles.Clear;
lbFiles.Items.AddStrings(FFileList); // копируем список файлов
FreeAndNil(FFileList);
FScanTask := nil; // Очищаем задачу
btnScan.Enabled := true;
end;
Вроде все понятно, но что делать, если пользователь закрывает программу, в то время как второстепенный поток сканирования является активным? В этом случае мы обрабатываем событие OnFormCloseQuery где указываем фоновой задаче закончить свою работу.
test_23_BackgroundFileSearch.pas При выходе из программы во время активности фоновой задачи
procedure TfrmBackgroundFileSearchDemo.FormCloseQuery(Sender: TObject;
var CanClose: boolean);
begin
if assigned(FScanTask) then begin
FScanTask.Terminate;
FScanTask := nil;
CanClose := true;
end;
end;
Метод Terminate сделает две вещи – он скажет задаче, что нужно завершить работу, затем дождется ее завершения. После этого, мы просто очистим интерфейсную ссылку на задачу. После чего позволим программе завершится.
Далее мы рассмотрим метод ScanFolders который является главным методом фоновой задачи, да да – это тот самый метод, который мы передали в CreateTask.
Метод разбивает значение, переданное в параметре FolderMask на папку и маску поиска, а затем передает эти значение главному рабочему методу ScanFolder.
test_23_BackgroundFileSearch.pas ScanFolders
procedure ScanFolders(const task: IOmniTask);
var
folder: string;
mask : string;
begin
mask := task.ParamByName['FolderMask'];
folder := ExtractFilePath(mask);
Delete(mask, 1, Length(folder));
if folder <> '' then
folder := IncludeTrailingPathDelimiter(folder);
ScanFolder(task, folder, mask);
end;
ScanFolder сначала находит все подпапки выбранной папки и вызывает себя рекурсивно для каждой подпапки. Это означает, что сначала обработаются самые глубокие папки и затем алгоритм поиска поднимается к вершине дерева папки. При нахождении подпапки посылается сообщение MSG_SCAN_FOLDER в основной GUI-поток. В параметре этого сообщения посылается название обрабатываемой папки.
test_23_BackgroundFileSearch.pas ScanFolders
const
MSG_SCAN_FOLDER = 1;
MSG_FOLDER_FILES = 2;
procedure ScanFolder(const task: IOmniTask; const folder, mask: string);
var
err : integer;
folderFiles: TStringList;
S : TSearchRec;
begin
err := FindFirst(folder + '*.*', faDirectory, S);
if err = 0 then
try
repeat
if ((S.Attr and faDirectory) <> 0) and
(S.Name <> '.') and
(S.Name <> '..')
then
ScanFolder(task, folder + S.Name + '\', mask); // рекурсия
err := FindNext(S);
until task.Terminated or (err <> 0);
finally
FindClose(S);
end;
task.Comm.Send(MSG_SCAN_FOLDER, folder);
folderFiles := TStringList.Create;
try
err := FindFirst(folder + mask, 0, S);
if err = 0 then try
repeat
folderFiles.Add(folder + S.Name);
err := FindNext(S);
until task.Terminated or (err <> 0);
finally
FindClose(S);
end;
finally
task.Comm.Send(MSG_FOLDER_FILES, folderFiles);
end;
end;
Метод ScanFolder с помощью файловых функции FindFirst/FindNext/FindClose управляет циклом поиска файлов в папке. Каждый найденный по маске файл будет добавлен к внутреннему объекту TStringList. Когда просмотр папки будет закончен, этот объект посылается в главную нить программы как параметр сообщения MSG_FOLDER_FILES.
Такой подход - посылка данных для одной папки - является компромиссом между возвращением полного набора (полное просмотренное дерево) и возвратом каждого файла в отдельности. В первом случае – это не обеспечивало бы хорошую обратную связь, во втором излишне нагружало бы систему.
Оба поисковых цикла проверяют статус задачи с помощью функции Terminated и если она возвращает True то цикл будет прерван немедленно. Такой код позволяет нам завершить фоновую задачу, когда пользователь закрывает приложение во время сканирования (при вызове обработчика OnFormCloseQuery).
Это - все, что должно быть сделано в фоновой задаче, но не все, что касается целиком программы. Мы еще должны обработать сообщения в GUI-потоке (основном потоке программы). Для этого мы напишем обработчик OTLMONITOR OnTaskMessage.
test_23_BackgroundFileSearch.pas Обработчик сообщений в GUI-потоке
procedure TfrmBackgroundFileSearchDemo.OTLMonitorTaskMessage(
const task: IOmniTaskControl);
var
folderFiles: TStringList;
msg : TOmniMessage;
begin
task.Comm.Receive(msg);
if msg.MsgID = MSG_SCAN_FOLDER then
FWaitingMessage := msg.MsgData
else
if msg.MsgID = MSG_FOLDER_FILES then
begin
folderFiles := TStringList(msg.MsgData.AsObject);
FFileList.AddStrings(folderFiles);
FreeAndNil(folderFiles);
FWaitingCount := IntToStr(FFileList.Count);
end;
end;
При получении сообщения MSG_SCAN_FOLDER, мы копируем название папки в локальную переменную. Если пришло сообщение - MSG_FOLDER_FILES, мы скопируем переданные в параметре сообщения имена найденных файлов (TStringList) в глобальный объект FFileList,. А также обновим число найденных в настоящее время файлов.
Почему же мы не обновляем сразу список файлов и счетчик на форме при приеме сообщения. В этом нет никакого смысла, так как фоновая задача может послать много сообщений (когда в папке много файлов удовлетворяющих критерию поиска) в секунду времени и нет никакого смысла обрабатывать их всех по очереди (постоянно перерисовывая GUI), это замедлило бы работу программы и привело бы к излишней нагрузке на Windows.
Поэтому хорошим тоном является немного другой подход. Вместо постоянного обновления по событию, Мы будем обновлять наши данные по таймеру, который будет вызываться три раза в секунду. Это конечно не позволит показать все просмотренные папки и все промежуточные результаты, но все-же предоставит пользователю приемлемую обратную связь без излишней потери скорости работы.
test_23_BackgroundFileSearch.pas Обновление данных на форме по таймеру
procedure TfrmBackgroundFileSearchDemo.tmrDisplayStatusTimer(Sender: TObject);
begin
if FWaitingMessage <> '' then begin
outScanning.Text := FWaitingMessage;
FWaitingMessage := '';
end;
if FWaitingCount <> '' then begin
outFiles.Text := FWaitingCount;
FWaitingCount := '';
end;
end;
На этом засим отклонятся, полностью функциональный фоновый сканер файла, готов.
Комментариев нет:
Отправить комментарий