Comments 45
там же и так достаточно асинхронности. Рисующий поток отделен от потоков в которых обрабатываются клики и прочее
ам же и так достаточно асинхронности.
Подскажите стандартный виджет показа асинхронной операции.
я когда инсталлер для рантайма писал, то в слоте, вызываемом кликом по кнопке в цикле проводил распаковку и вызывл обновление значения в прогрессбаре — интерфейс не зависал. В однопоточных тулкитах при таком сценарии интерфейс зависает и не обновляется, пока не завершится обработчик события по клику.
Алсо, если погуглите, то найдете попытки некоторых разработчиков сделать обработку клика синхронной, потому что дабл клик может отправить в очередь сигнал для обработки еще одного клика, до завершения обработки предыдущего
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
property string prop: ""
onClicked: {
console.log("CLICKED");
for (var i = 0; i < 10000000; i++) {
prop = "AAA";
}
console.log("DONE");
}
}
}
Пока крутится цикл (не написало «DONE» в консоль), можно кликнуть по окну еще один раз, и после «DONE» сразу же напишет «CLICKED», т.е. обработчик будет вызван еще один раз. У меня именно так и работает, и в этом нет ничего неожиданного.
в конструкторе окна делал (начледник QMainWindow)
А также обрабатывая случаи завершения (on_completed) и возникновения ошибки (on_error)
в входной последовательности.
Для C++ есть библиотека rxcpp.
Псевдокод:
auto srcValuesThread = rxcpp::observe_on_new_thread();
auto uiThread = rxcpp::observe_on_new_thread();
auto period = std::chrono::milliseconds(1);
auto values = rxcpp::observable<>::interval(period);
values
// отфильтровать только четные значения
.filter([](int i){ return i % 2 == 0})
// обработать только 3 значения
.take(3)
// преобразование какждое значение
.map([](int i){ return i * 100});
// продолжить обработку на потоке uiThread
.observeOn(uiThread)
// выполнить подписку и запустить генерацию значений на потоке srcValuesThread
.subscribe_on(srcValuesThread)
.subscribe(
// on_next
[this](int v){ this->labelWidget.setText("OnNext:");}
// on_error
[this](std::eception_ptr eptr){ this->labelWidget.setText("OnError"); },
// on_completed
[this](){ this->labelWidget.setText("OnCompleted");});
На мой взгляд ценность мое библиотеки в том, что она задает хорошие дефолты для состояние Progress и Error. И готовые виджеты, которые из коробки отобразят Progress и Error. Пользователю лишь надо указать как вычислять асинхронный результат (возможно и с помощью rxcpp) и предоставить метод-фабрику виджета, который отобразит этот результат. Вся остальная машинерия делается библиотекой.
Например в вашем примере я не вижу как можно отобразить прогресс асинхронной операции, как передать процент сделанного в этот виджет, так можно отменить операцию, если пользователь нажал кнопку Стоп или система знает, что результат операции уже не нужен.
Тут больше подойдет акторный подход, где каждый работает в своем потоке и никакие мьютексы не нужны.
А для того, чтобы не копировать значения каждый раз (хотя мне кажется это надуманной проблемой) можно изобрести COW
Можете привести схемотичный пример с акторами, хотелось бы посмотреть как там можно решить подобные проблемы.
Так а std::atomic?
Помнится, в C++11 подвезли стандартизированную модель памяти с атомарными операциями которые потокобезопасны. Уже 2019-й и вышел C++17, так что 11-й стандарт вполне можно брать за минимум, если это не какой-то жуткий легаси, под который, кстати, Qt 5 тоже не соберётся, т.к. там как раз за основу C++11 принят
Зачем переизобретать велосипед?
Или я не понял сути вашего вопроса?
Так а std::atomic?
относится к моему высказыванию
Те же future/promise кажется используют мютексы,
но наверное он относился ко второй части моего сообщения
Можете привести схемотичный пример с акторами, хотелось бы посмотреть как там можно решить подобные проблемы.
Просто мой класс AsyncValue в чем-то похож на std::future, в котором нет атомиков.
Насколько я теперь понимаю, вы предлагаете акторы с атомиками. Скорее всего там внутри будут какие-то очереди, динамические аллокации, чего хотелось бы избежать. Если я не прав, поправьте.
Плюс, если использовать std::atomic, то точно ничего не сломается/не вернётся назад во времени или значении) Если же вам нужны контейнеры STL, то да, они не безопасны, но на то есть std::mutex, например.
В процессе работы (и по окончании) поток бизнес логики посылает сигналы о результате выполнения операции. Чтобы было понятно, к какой операции пришел сигнал, можно передавать id операции.
То есть, мы запускаем операцию
b.runOperation(id);
он в ответ посылает сигналы
operationFinished(id);
Можно вместо id использовать более продвинутые подходы, например коллбэки
Есть 2 потока, один поток с гуи, другой с бизнес-логикой. Поток с бизнес логикой будет большую часть времени ничего не делать, но при получении сигнала начнет работать над задачей. При этом так как весь поток бизнес-логи принадлежит классу бизнес-логики, то никаких мьютексов, атомиков и других средств синхронизации не требуется.
Вы сделали очень сильные предположения, с ними, конечно, никакие локи не нужны.
В реальности бизнес-логика работает на пуле потоков с достаточно развесистой системой зависимостей, когда результатом работы одной операции пользуются несколько зависимых операций и тому подобное.
Тут важнее взаимодействие между классом gui и классом бизнес-логики.
Предположим.
И для класса gui класс бизнес-логики выглядит как однопоточный класс, в котором ничего нельзя изменить извне этого потока
Как вы это гарантируете, кроме как на словах? И не сильное ли это ограничение?
При этом виджетов для отображения асинхронных действий в Qt я не нашел (возможно плохо искал, поправьте, если я ошибаюсь).
Стандартные виджеты из коробки "асинхронные". Стандартные виджеты поддерживают сигналы/слоты, которые чудесно работают между потоками. А там где у нас нет сигналов(std::thread, QRunnable и т.д.) мы всегда можем дернуть слот через QMetaObject::invokeMethod c Qt::QueuedConnection.
Я описал одну из проблем, вот у вас прогресс идет и асинхронная операция закончилась, надо удалить прогресс и положить вместо него результат операции, но пока идет рассылка сигналов, удалять прогресс нельзя, то есть нужно держать и прогресс и результат одновременно. И заменять одно на другое, очевидно, под локом
надо удалить прогресс и положить вместо него результат операции
Заводишь третий сигнал finished, в котором передаешь вычисленное значение(передавать можно как по значению, так и по указателю). Соответственно у вас все происходит атомарно и без локов.
Тогда получается сам этот объект вырождается просто в сигнал. Это единственное, что он может выдать заинтересованным клиентам без локов. Но как клиены возьмут текущее состояние объекта без локов, когда сигнал ни разу еще не запустился? Как сделать wait?
Как избежать ситуации, когда мы рассылаем сигнал с результатом, а в каком-то другом потоке изменили/пересчитали результат и нужно послать опять сигнал с новым результатом. Как запретить параллельные рассылки сигналов?
Ну как бы обычно смысл в том, что «пересчитать результат» в конкретном случае может только один поток
да, но берем мы этот поток из пула потоков (мы же не резервируем на каждый тип расчета по потоку, потоков не напасёшся). Предыдущий рассчет на другом потоке рассылает сигнал, как нам определить есть ли сейчас текущий рассчет или надо новый поток брать и считать?
А причём тут пул потоков? Он нужен только лишь для того, чтобы не пересоздавать поток каждый раз, и все. На независимость задач это не влияет. Как только поток заканчивает расчёт своей независимой части, он посылает соответствующий сигнал, скажем, finished(), так вы и узнаете, закончен он или нет. Я что-то не понимаю проблемы пока.
Ну да. Бывают, конечно, задачи, где такое затруднительно, но в основном все-таки декомпозиция обычно получается.
По моему мнению библиотечные классы должны быть просты и удобны в использовании. Реализация может быть сложноватой (на то она и библиотека) с понятными накладными расходами на использование. При этом желательно, чтобы неправильное использование класса приводило к ошибкам компиляции или хотя бы рантайм проверкам.
Таким образом появился AsyncValue класс — такой std::future на стероидах (на мой взгляд). Ну а виджеты — простое и удобное дополнение к ним.
На самом деле Qt имеет все механизмы для комфортной и прозрачной асинхронной работы, как примитивы (сигналы/слоты), так и вещи по управлению потоком выполнения асинхронных задач (QFuture и иже с ним).
Если хотябы от этих проблем есть возможность избавиться, возможно из AsyncValue и выйдет какой-то толк
Самописный виджет конечно можно использовать, но в этом и суть библиотек, что они дают хорошие дефолтные реализации с возможностью кастомизации. Моя библиотеки именно об этом.
Библиотека — попытка обобщить такой подход и выделить повторяющийся код.
Возможно демо в картинкой из проекта вам даст лучшее представление. У нас есть некий GUI (эдитбокс с именем файла) который запускает асинхронную загрузку картинки. Виджет из библиотеки показывает прогресс, пока идет загрузка и ошибку если загрузить не удалось и картинку в случае успеха. Пользователю библиотеки нужно лишь указать как грузить картинку и как ее показывать в GUI, а всю работу с прогрессом и ошибками библиотека берет на себя.
Библиотека асинхронных виджетов qt-async