Пример за басейн с нишки на Delphi, използвайки AsyncCalls

Това е следващият ми тестов проект, за да видя каква библиотека за резби за Delphi би ми подхождала най-добре за моята задача „сканиране на файлове“, която бих искал да обработя в няколко нишки / в пула с нишки.

За да повторя целта си: трансформирам последователното си "сканиране на файлове" от 500-2000 + файла от подхода, който не е с резба, в един с резба. Не трябва да има 500 нишки, работещи наведнъж, като по този начин бих искал да използвам пул с нишки. Пулът с нишки е клас, подобен на опашката, който подава редица изпълняващи нишки със следващата задача от опашката.

Първият (много основен) опит беше направен чрез просто разширяване на класа TThread и прилагане на метода Execute (моят анализатор на нишки с нишки).

Тъй като Delphi няма клас пулове на нишки, реализиран извън кутията, във втория ми опит опитах да използвам OmniThreadLibrary от Primoz Gabrijelcic.

OTL е фантастичен, има милиони начини за изпълнение на задача във фонов режим, начин, който трябва да се извърши, ако искате да имате "огън и забравете" подход за предаване на резбовано изпълнение на части от вашия код.

instagram viewer

AsyncCalls от Andreas Hausladen

Забележка: Това, което следва, ще бъде по-лесно да се следва, ако първо изтеглите изходния код.

Докато проучвам повече начини за изпълнение на някои от функциите ми с резба, реших да изпробвам и модула "AsyncCalls.pas", разработен от Andreas Hausladen. Анди AsyncCalls - Асинхронни повиквания на функции unit е друга библиотека, която разработчикът на Delphi може да използва, за да облекчи болката от прилагането на резбован подход при изпълнение на някакъв код.

От блога на Анди: С AsyncCalls можете да изпълнявате няколко функции едновременно и да ги синхронизирате във всяка точка на функцията или метода, който ги е стартирал... Единицата AsyncCalls предлага разнообразие от прототипи на функции за извикване на асинхронни функции... Той реализира пул с нишки! Инсталацията е супер лесна: просто използвайте asynccalls от която и да е от своите единици и имате незабавен достъп до неща като „изпълнете в отделна тема, синхронизирайте основния потребителски интерфейс, изчакайте, докато приключи“.

Освен свободния за използване (MPL лиценз) AsyncCalls, Анди често публикува свои собствени корекции за IDE на Delphi като „Ускоряване на Delphi" и "DDevExtensions„Сигурен съм, че сте чували (ако вече не използвате).

AsyncCalls в действие

По същество всички функции на AsyncCall връщат IAsyncCall интерфейс, който позволява да се синхронизират функциите. IAsnycCall разкрива следните методи:

 //v 2,98 от asynccalls.pas
IAsyncCall = интерфейс
// изчаква, докато функцията приключи и върне връщаната стойност
функция Синхронизиране: Целочислено;
// връща True, когато функцията за асинхрон приключи
функция завършена: Булева;
// връща стойността за връщане на функцията на асинхрон, когато Finished е TRUE
функция ReturnValue: Integer;
// казва на AsyncCalls, че зададената функция не трябва да се изпълнява при текущата заплаха
процедура ForceDifferentThread;
край;

Ето примерно обаждане към метод, очакващ два целочислени параметъра (връщане на IAsyncCall):

 TAsyncCalls. Invoke (AsyncMethod, i, Random (500));
функция TAsyncCallsForm. AsyncMethod (taskNr, sleepTime: integer): цяло число;
започвам
резултат: = sleepTime;
Sleep (sleepTime);
TAsyncCalls. VCLInvoke (
процедура
започвам
Дневник (Формат ('свършено> nr:% d / задачи:% d / спал:% d', [tasknr, asyncHelper). TaskCount, sleepTime]));
край);
край;

TAsyncCalls. VCLInvoke е начин за синхронизация с основната нишка (основната тема на приложението - потребителския интерфейс на приложението ви). VCLInvoke се връща веднага. Анонимният метод ще бъде изпълнен в основната нишка. Има и VCLSync, който се връща, когато анонимният метод е извикан в основната нишка.

Резбован басейн в AsyncCalls

Назад към моята задача за „сканиране на файлове“: при подаване (в цикъл за) обединението на нишката на asynccalls със серия от TAsyncCalls. Извиквайте () повиквания, задачите ще бъдат добавени към вътрешния пул и ще се изпълняват „когато дойде време“ (когато преди добавените разговори приключиха).

Изчакайте всички IAsyncCalls да приключат

Функцията AsyncMultiSync, дефинирана в asnyccalls, чака извикванията на async (и други дръжки) да приключат. Има няколко претоварен начини да се обадите на AsyncMultiSync и ето най-простият:

функция AsyncMultiSync (конст Списък: масив от IAsyncCall; WaitAll: Boolean = True; Милисекунди: Cardinal = INFINITE): Кардинал; 

Ако искам да бъда изпълнен „изчакайте всички“, трябва да попълня масив от IAsyncCall и да направя AsyncMultiSync на филийки от 61.

Моят помощник на AsnycCalls

Ето парче от TAsyncCallsHelper:

ВНИМАНИЕ: частичен код! (пълен код на разположение за изтегляне)
употреби AsyncCalls;
Тип
TIAsyncCallArray = масив от IAsyncCall;
TIAsyncCallArrays = масив от TIAsyncCallArray;
TAsyncCallsHelper = клас
частен
fTasks: TIAsyncCallArrays;
Имот Задачи: TIAsyncCallArrays Прочети fTasks;
обществен
процедура AddTask (конст повикване: IAsyncCall);
процедура WaitAll;
край;
ВНИМАНИЕ: частичен код!
процедура TAsyncCallsHelper. WaitAll;
Var
i: цяло число;
започвам
за i: = високо (задачи) Downto Ниска (Задачи) правя
започвам
AsyncCalls. AsyncMultiSync (Задачи [i]);
край;
край;

По този начин мога да "чакам всички" на парчета от 61 (MAXIMUM_ASYNC_WAIT_OBJECTS) - тоест да чакам масиви от IAsyncCall.

С горното основният ми код за захранване на пула с нишки изглежда:

процедура TAsyncCallsForm.btnAddTasksClick (Подател: TObject);
конст
nrItems = 200;
Var
i: цяло число;
започвам
asyncHelper. MaxThreads: = 2 * Система. CPUCount;
ClearLog ( "изходен");
за i: = 1 до nrItems правя
започвам
asyncHelper. AddTask (TAsyncCalls). Invoke (AsyncMethod, i, Random (500)));
край;
Влезте („all in“);
// изчакайте всички
//asyncHelper.WaitAll;
// или разрешете анулирането на всичко, което не е започнало, като кликнете върху бутона „Отказ на всички“:

докато НЕ asyncHelper. AllFinished правя Приложение. ProcessMessages;
Вход ( "завършен");
край;

Анулиране на всички? - Трябва да промените AsyncCalls.pas :(

Бих искал също така да има начин да „отменя“ онези задачи, които са в пула, но чакат изпълнението им.

За съжаление, AsyncCalls.pas не предоставя прост начин за анулиране на задача, след като бъде добавен към пула с нишки. Няма IAsyncCall. Отказ или IAsyncCall. DontDoIfNotAlreadyExecuting или IAsyncCall. NeverMindMe.

За да работи това, трябваше да променя AsyncCalls.pas, като се опитвах да го променя възможно най-малко - така че когато Анди пуска нова версия, трябва само да добавя няколко реда, за да имам идеята си за „Отказ“ работи.

Ето какво направих: Добавих „Отказ от процедура“ към IAsyncCall. Процедурата за отмяна задава полето "FCancegled" (добавено), което се проверява, когато пулът е на път да започне изпълнението на задачата. Трябваше леко да променя IAsyncCall. Завършен (така че отчетите за разговори завършват дори при анулиране) и TAsyncCall. InternExecuteAsyncCall процедура (да не се изпълнява повикването, ако е било отменено).

Можеш да използваш WinMerge за лесно намиране на разликите между оригиналния asynccall.pas на Анди и променената ми версия (включена в изтеглянето).

Можете да изтеглите пълния изходен код и да проучите.

изповед

ЗАБЕЛЕЖКА! :)

Най- CancelInvocation метод спира блокирането на AsyncCall. Ако AsyncCall вече е обработен, повикването към CancelInvocation няма ефект и функцията Canceled ще върне False, тъй като AsyncCall не е отменен.
Най- Отменен метод връща True, ако AsyncCall е бил отменен от CancelInvocation.
Най- забравям метод премахва връзката на IAsyncCall интерфейса от вътрешния AsyncCall. Това означава, че ако последната препратка към интерфейса на IAsyncCall е изчезнала, асинхронният разговор все още ще бъде изпълнен. Методите на интерфейса ще хвърлят изключение, ако се извикат след извикване Forget. Функцията за асинхронизация не трябва да влиза в основната нишка, защото може да бъде изпълнена след TThread. Механизмът за синхронизиране / опашка е бил изключен от RTL, което може да доведе до мъртво заключване.

Имайте предвид обаче, че все още можете да се възползвате от моя AsyncCallsHelper, ако трябва да изчакате всички асинхронни обаждания да приключат с „asyncHelper. WaitAll "; или ако трябва да „Отказ“.

instagram story viewer