DevelNext – Асинхронность и многопоточность
JPHP позволяет использовать потоки и выполнять код в фоне, что позволяет избежать подтормаживания GUI при выполнении ресурсоёмких операций.
Если не создавать отдельные потоки, DevelNext будет исполнять всё в главном потоке: отрисовку графического интерфейса вперемешку с прочими вычислениями. При обработке “тяжёлых” файлов либо при других вычислениях, требующих много памяти или времени, отрисовка графического интерфейса произойдёт только после вычислений, что будет сопровождаться подтормаживанием и зависанием программы. Чтобы этого избежать – нужно все ресурсоёмкие вычисления выкидывать в отдельные потоки. Взаимодействие с формами и графическими элементами возможно только в главном потоке.
Для работы с потоками, есть класс Thread, для взаимодействия с главным (графическим) потоком существует класс UXApplication (он позволяет из любого потока вернуться в графический).
Использование конструктора
Допустим, необходимо загрузить изображение из Интернета в объект “изображение”. Для этого есть все необходимые элементы в конструкторе.
1. Добавим на форму объекты Изображение (image) и Кнопка (button), на кнопку добавим событие Действие => Изменить в конструкторе
2. В конструкторе сначала добавим индикатор загрузки (preloader), загрузим изображение (нужно убрать галочку “в главном потоке”), а после загрузки уберём индикатор.
Если перевести действия в код:
1 2 3 4 5 6 7 |
use action\Element; use php\io\Stream; $this->showPreloader('Загрузка ...'); Element::loadContentAsync($this->image, 'https://pp.vk.me/c604326/v604326348/1abb7/IgF2jCor3Js.jpg', function () use ($event) { $this->hidePreloader(); }); |
После нажатия на кнопку, мы увидим сначала preloader, а по окончанию загрузки – изображение, и всё без лагов и подтормаживаний.
Только код
Мы рассмотрели возможность выполнения кода в фоне, используя конструктор. Следующая ситуация, необходимо скачать из Интернета список стран, распарсить его и отобразить в списке. В конструкторе недостаточно возможностей, поэтому придётся всё писать вручную.
Последовательность действий:
- 1. Скачать список стран (можно получить из api контакта)
- 2. Распарсить данные в массив (вк возвращает данные в формате json)
- 3. Отобразить данные в виде списка
Пункты 1 и 2 необходимо выполнить в отдельном потоке, 3 – в главном.
Приступим.
Сначала создадим новый метод, который позволит выполнить код асинхронно (т.е. в отдельном потоке). В отдельном потоке будет выполнена функция $func, всё, что она вернёт, будет передано в функцию $callback, которая потом будет выполнена в главном потоке.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
use php\gui\UXApplication; use php\lang\Thread; /** * Выполнить функцию асинхронно * @param callable $func - функция, которая будет вызвана в отдельном потоке * @param callable $callback - функция, которая будет вызвана в главном потоке, когда завершится выполнение $func */ function aSync($func, $callback){ // Создадим новый поток, в него передадим наши функции $func, $callback $thread = new Thread(function() use ($func, $callback){ // В отдельном потоке выполним $func, если она что-то вернёт, сохраним это в $return $return = $func(); // runLater позволяет вернуться в главный поток UXApplication::runLater(function () use ($callback, $return) { $callback($return); // В главном потоке выполним $callback и передадим в неё результат выполнения функции $func - $return $this->hidePreloader(); // По окончанию всех действий прячем индикатор }); }); $this->showPreloader('Загрузка'); // Перед потоком покажем индикатор $thread->start(); // Запуск потока } |
(в других проектах, если понадобится работа с потоками, можно использовать эту функцию)
Получаем список стран и парсим json данные.
1 2 3 4 |
use php\io\Stream; $data = Stream::getContents('https://api.vk.com/method/database.getCountries?need_all=1&v=5.33'); $countries = json_decode($data, true); |
Код есть, объединяем всё в один проект. Создаем элементы кнопка (button) и выпадающий список (combobox).
Весь код формы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php namespace app\forms; use php\lang\Thread; use php\gui\UXApplication; use php\io\Stream; use php\gui\framework\AbstractForm; use php\gui\event\UXEvent; class MainForm2 extends AbstractForm { /** * Событие - клик на кнопку * @event button.action */ function doButtonAction(UXEvent $event = null) { // Получаем список стран асинхронно $getCountries = function(){ $data = Stream::getContents('https://api.vk.com/method/database.getCountries?need_all=1&v=5.33'); $countries = json_decode($data, true); return $countries['response']['items']; }; // Отобразим список стран $showData = function($countries){ foreach($countries as $country){ $this->combobox->items->add($country['title']); } }; $this->aSync($getCountries, $showData); } /** * Выполнить функцию асинхронно * @param callable $func - функция, которая будет вызвана в отдельном потоке * @param callable $callback - функция, которая будет вызвана в главном потоке, когда завершится выполнение $func */ function aSync($func, $callback){ // Создадим новый поток, в него передадим наши функции $func, $callback $thread = new Thread(function() use ($func, $callback){ // В отдельном потоке выполним $func, если она что-то вернёт, сохраним это в $return $return = $func(); // runLater позволяет вернуться в главный поток UXApplication::runLater(function () use ($callback, $return) { $callback($return); // В главном потоке выполним $callback и передадим в неё результат выполнения функции $func - $return $this->hidePreloader(); // По окончанию всех действий прячем индикатор }); }); $this->showPreloader('Загрузка'); // Перед потоком покажем индикатор $thread->start(); // Запуск потока } } |
После нажатия на кнопку появится индикатор, когда данные загрузятся, индикатор исчезнет
В заключение
Проект со всеми примерами из этого поста: http://develnext.org/project/NOWKNcSHCJ
Код для создания асинхронных запросов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
use php\gui\UXApplication; use php\lang\Thread; /** * Выполнить функцию асинхронно * @param callable $func - функция, которая будет вызвана в отдельном потоке * @param callable $callback - функция, которая будет вызвана в главном потоке, когда завершится выполнение $func */ function aSync($func, $callback){ // Создадим новый поток, в него передадим наши функции $func, $callback $thread = new Thread(function() use ($func, $callback){ // В отдельном потоке выполним $func, если она что-то вернёт, сохраним это в $return $return = $func(); // runLater позволяет вернуться в главный поток UXApplication::runLater(function () use ($callback, $return) { $callback($return); // В главном потоке выполним $callback и передадим в неё результат выполнения функции $func - $return $this->hidePreloader(); // По окончанию всех действий прячем индикатор }); }); $this->showPreloader('Загрузка'); // Перед потоком покажем индикатор $thread->start(); // Запуск потока } |
1 2 3 4 5 6 7 |
(new Thread(function() use ($data){ // C помощью use передаем переменные из функции в функцию, из потока в поток // Тут что-то делаем в фоне // Переменную $result возвращаем в главный поток uiLater(function () use ($result) { // Тут что-то делаем в главном потоке }); }))->start(); |
Как вновь запустить поток, после того как он выполнился? Мне нужна бесконечная рекурсия. На ум только страшные схемы приходят. Как это оформить кратко и прилично? Мне не нужно сразу много потоков, мне нужно именно по завершении одного, чтобы запускался второй такой же, потом третий и так далее. Если вызывать из функции, которая происходит по завершении, то мы не будем знать о самой этой функции для вызова нового потока, если делать ещё дополнительные глобальные переменные то выходят страшные схемы.
Ладно, я смог это сделать через глобальные переменные. Теперь я не могу понять как остановить поток? =)
О, я и с этим справился =)
Спасибо за статью, а как разобрать данные полученные с таблицы.
В мой таблице очень много данных, мне нужно отобрать по имени и фамилии, занести все в массив
и вывести по ключам.
К примеру: name=’Alex’, fname = ‘Smirnov’; У него есть Value = 1,2,3 и.т.д. Нужно отобрать с таблицы, показать все значения value. (В таблице может быть более 1000 имен и фамилии).
В группе вк мне подсказали:
$arr = arr::toArray($this->table->items);
Json::toFile(‘./Config.json’,$arr);
Далее нужно делать потоки, в котором нужно отобрать. Как сделать?
,,
норм
Круто!
Отличный урок! Хорошо все расписал