Это мой следующий тестовый проект, чтобы увидеть, какая библиотека потоков для Delphi лучше всего подойдет для моей задачи «сканирования файлов», которую я хотел бы обрабатывать в нескольких потоках / в пуле потоков.
Повторяю мою цель: преобразовать мою последовательную «проверку файлов» из 500-2000+ файлов из многопоточного подхода в многопоточный. Я не должен иметь 500 потоков, работающих одновременно, поэтому хотел бы использовать пул потоков. Пул потоков - это класс, похожий на очередь, который подает несколько запущенных потоков следующей задачей из очереди.
Первая (очень простая) попытка была сделана простым расширением класса TThread и реализацией метода Execute (мой синтаксический анализатор строк).
Поскольку Delphi не имеет встроенного класса пула потоков, во второй попытке я попытался использовать OmniThreadLibrary от Primoz Gabrijelcic.
OTL - это фантастика, у него есть миллионы способов выполнить задачу в фоновом режиме, способ пойти, если вы хотите использовать метод «запуска и забытия» для обработки многопоточного выполнения фрагментов вашего кода.
AsyncCalls Андреас Хаусладен
Примечание: за тем, что следует, будет легче следовать, если вы сначала загрузите исходный код.
Разрабатывая дополнительные способы выполнения некоторых из моих функций многопоточным способом, я решил также попробовать модуль «AsyncCalls.pas», разработанный Андреасом Хаусладеном. Энди AsyncCalls - асинхронные вызовы функций unit - это еще одна библиотека, которую разработчик Delphi может использовать, чтобы облегчить задачу реализации многопоточного подхода к выполнению некоторого кода.
Из блога Энди: С AsyncCalls вы можете выполнять несколько функций одновременно и синхронизировать их в каждой точке функции или метода, который их запустил... Модуль AsyncCalls предлагает множество прототипов функций для вызова асинхронных функций... Он реализует пул потоков! Установка очень проста: просто используйте asynccalls из любого из ваших устройств, и вы получите мгновенный доступ к таким вещам, как «выполнить в отдельном потоке, синхронизировать основной интерфейс, дождаться завершения».
Помимо бесплатного использования (лицензия MPL) AsyncCalls, Энди также часто публикует свои исправления для Delphi IDE, например "Delphi Speed Up" и "DDevExtensions«Я уверен, что вы слышали (если не используете уже).
AsyncCalls в действии
По сути, все функции AsyncCall возвращают интерфейс IAsyncCall, который позволяет синхронизировать функции. IAsnycCall предоставляет следующие методы:
//v 2.98 из asynccalls.pas
IAsyncCall = интерфейс
// ждет завершения функции и возвращает возвращаемое значение
Функция Sync: Integer;
// возвращает True, когда асинхронная функция завершена
Функция Закончена: Булево;
// возвращает возвращаемое значение асинхронной функции, когда Finished равен TRUE
function ReturnValue: Integer;
// сообщает AsyncCalls, что назначенная функция не должна выполняться в текущей последовательности
процедура ForceDifferentThread;
конец;
Вот пример вызова метода, ожидающего два целочисленных параметра (возвращающих IAsyncCall):
TAsyncCalls. Invoke (AsyncMethod, i, Random (500));
функция TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): целое число;
начать
результат: = sleepTime;
Sleep (sleepTime);
TAsyncCalls. VCLInvoke (
процедура
начать
Журнал (Формат ('сделано> номер:% d / tasks:% d / slept:% d', [tasknr, asyncHelper. TaskCount, sleepTime]));
конец);
конец;
TAsyncCalls. VCLInvoke - это способ синхронизации с вашим основным потоком (основной поток приложения - пользовательский интерфейс вашего приложения). VCLInvoke возвращается немедленно. Анонимный метод будет выполнен в основном потоке. Также есть VCLSync, который возвращает, когда анонимный метод был вызван в основном потоке.
Пул потоков в AsyncCalls
Вернемся к моей задаче «сканирование файлов»: при подаче (в цикле for) пула потоков asynccalls сериями TAsyncCalls. Вызовы Invoke (), задачи будут добавлены во внутренний пул и будут выполняться «когда придет время» (когда завершатся ранее добавленные вызовы).
Подождите, пока все IAsyncCalls завершат
Функция AsyncMultiSync, определенная в asnyccalls, ожидает завершения асинхронных вызовов (и других дескрипторов). Есть несколько перегруженный способы вызова AsyncMultiSync, и вот самый простой:
функция AsyncMultiSync (Const Список: массив IAsyncCall; WaitAll: Boolean = True; Миллисекунды: кардинал = бесконечный): кардинал;
Если я хочу реализовать «все ожидания», мне нужно заполнить массив IAsyncCall и сделать AsyncMultiSync срезами по 61.
My AsnycCalls Helper
Вот часть TAsyncCallsHelper:
ВНИМАНИЕ: частичный код! (полный код доступен для скачивания)
использования AsyncCalls;
тип
TIAsyncCallArray = массив IAsyncCall;
TIAsyncCallArrays = массив TIAsyncCallArray;
TAsyncCallsHelper = учебный класс
частный
fTasks: TIAsyncCallArrays;
свойство Задачи: TIAsyncCallArrays читать fTasks;
общественности
процедура AddTask (Const вызов: IAsyncCall);
процедура WaitAll;
конец;
ВНИМАНИЕ: частичный код!
процедура TAsyncCallsHelper. WaitAll;
вар
я: целое число;
начать
за я: = высокий (задачи) Downto Низкий (Задачи) делать
начать
AsyncCalls. AsyncMultiSync (Tasks [i]);
конец;
конец;
Таким образом, я могу «подождать все» порциями по 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - то есть ожидать массивов IAsyncCall.
С учетом вышеизложенного мой основной код для подачи в пул потоков выглядит так:
процедура TAsyncCallsForm.btnAddTasksClick (Отправитель: TObject);
Const
nrItems = 200;
вар
я: целое число;
начать
asyncHelper. MaxThreads: = 2 * Система. CPUCount;
ClearLog ( 'начиная');
за я: = 1 к nrItems делать
начать
asyncHelper. AddTask (TAsyncCalls. Invoke (AsyncMethod, i, Random (500)));
конец;
Журнал («все в»);
// ждать всех
//asyncHelper.WaitAll;
// или разрешить отмену всех не запущенных нажатием кнопки «Отменить все»:
в то время как не asyncHelper. Все закончено делать Заявка. ProcessMessages;
Log ( 'законченный');
конец;
Отменить все? - Нужно изменить AsyncCalls.pas :(
Я также хотел бы иметь возможность «отменять» те задачи, которые находятся в пуле, но ожидают их выполнения.
К сожалению, AsyncCalls.pas не предоставляет простой способ отмены задачи после ее добавления в пул потоков. Там нет IAsyncCall. Отмена или IAsyncCall. DontDoIfNotAlreadyExecuting или IAsyncCall. NeverMindMe.
Чтобы это работало, мне пришлось изменить AsyncCalls.pas, пытаясь изменить его как можно меньше - так что когда Энди выпускает новую версию, мне нужно всего лишь добавить несколько строк, чтобы у меня появилась идея «Отменить задание» работает.
Вот что я сделал: я добавил «Отмена процедуры» в IAsyncCall. Процедура Отмена устанавливает поле «FCancelled» (добавлено), которое проверяется, когда пул собирается приступить к выполнению задачи. Мне нужно было немного изменить IAsyncCall. Завершено (чтобы отчеты о вызовах заканчивались даже после отмены) и TAsyncCall. Процедура InternExecuteAsyncCall (не выполнять вызов, если он был отменен).
Ты можешь использовать WinMerge чтобы легко найти различия между исходным asynccall.pas Энди и моей измененной версией (включена в загрузку).
Вы можете скачать полный исходный код и изучить.
исповедь
ВНИМАНИЕ! :)
CancelInvocation метод останавливает вызов AsyncCall. Если AsyncCall уже обработан, вызов CancelInvocation не имеет никакого эффекта, и функция Cancelled вернет False, поскольку AsyncCall не был отменен.
отменен Метод возвращает True, если AsyncCall был отменен с помощью CancelInvocation.
забывать Метод освобождает интерфейс IAsyncCall от внутреннего AsyncCall. Это означает, что если последняя ссылка на интерфейс IAsyncCall исчезнет, асинхронный вызов все равно будет выполнен. Методы интерфейса вызовут исключение, если они будут вызваны после вызова Forget. Асинхронная функция не должна вызывать основной поток, потому что она может быть выполнена после TThread. Механизм синхронизации / очереди был отключен RTL, что может привести к мертвой блокировке.
Тем не менее, обратите внимание, что вы все еще можете воспользоваться моим AsyncCallsHelper, если вам нужно дождаться завершения всех асинхронных вызовов с "asyncHelper. WaitAll "; или если вам нужно «Отменить все».