Согласование потоков Qt и не только. Активная 'посылка'
Решил поиграться с библиотекой Wt для написания веб сервисов на c++ и столкнулся с интересной задачей согласования произвольных потоков и Qt фреймворка, а точнее как доставить событие нажатия Wt::WButton в мой Qt::QWidget.
В общем случае задача проста - есть колбэк из произвольного(не Qt) потока и надо выполнить действие над обычным QObject‘ом.
Мне, почему-то, сразу пришла в голову следующая аналогия: Qt это огромный футуристический город из вращающихся по своим законам шестерёнок, в котором могут легко жить только исконно местные жители - КуЖители, общающиеся на своём ‘языке сигналов‘. А я(сторонний фреймворк) стою перед входом в город с сообщением, которое надо передать одному из этих жителей.
Теперь немного о том как это вообще можно сделать:
- Ринуться с посылкой прямо в город, рискуя быть перемолотым шестернями(нарваться на гонки потоков):
- Создать QObject с необходимым сигналом, подключить его к слоту назначения и вызвать, отдав всю работу по согласованию Qt. В этом способе есть ряд недостатков:
- если у меня есть 10 кнопок(Wt::WButton), которые уже имеют Wt-сигналы 'clicked', зачем плодить еще 10 Qt-сигналов buttonClicked()?
- если мне надо не просто дернуть Qt-слот, а выполнить ряд действий над объектом? - Использовать собственные велосипеды для синхронизации - мьютексы, вэйт-кондишены и пр.
MyQObject* object;
wtbutton.clicked().connect(object, &MyQObject::doSomethig);
//Или если используем лямбды:
wtbutton.clicked().connect([object] () {
object->doSomething();
object->doSomethingAnother();
});
Тут, думаю, можно еще предложить решения, но это уже в коментах. А я решил сделать 'посылку': идея в том, чтобы эта посылка сама была местным КуЖителем, дошла до назначения своим ходом и взорваласьсработала:
MyQObject* object;
wtbutton.clicked().connect([object] () {
new Package(object, [object] () {
object->doSomething();
object->doSomethingOther();
});
});
Здесь колбэк 'clicked' срабатывает из стороннего потока, при этом создается объект посылки, которая доставляется в поток получателя, где выполняет собственно действия(открывает получатель посылку, а тут на тебе). Важно то, что поток вызывающий clicked() не блокируется и продолжает выполнение ДО того как посылка доставлена. Если надо блокировать то выглядит это так(можно думаю добавить еще сахару, но и так сойдет):
MyQObject* object;
wtbutton.clicked().connect([object] () {
new Package(object, [object] () {
object->doSomething();
object->doSomethingOther();
},
Qt::BlockingQueuedConnection);
});
Тут вызывающий поток заблокируется, подождет доставки посылки, и только потом продолжит выполнение.
Как это работает?
Package в конструкторе передает себя под управление потока, который обслуживает объект назначения. Затем он делает invokeMethod, а в методе исполняет переданную лямбду. QMetaObject::invokeMethod, аргументом принимает Qt::ConnectionType и таким образом можно сделать блокирующий вызов. А теперь сам код:
Как это работает?
Package в конструкторе передает себя под управление потока, который обслуживает объект назначения. Затем он делает invokeMethod, а в методе исполняет переданную лямбду. QMetaObject::invokeMethod, аргументом принимает Qt::ConnectionType и таким образом можно сделать блокирующий вызов. А теперь сам код:
class Package : public QObject {
Q_OBJECT
public:
template <typename F>
Package(QObject* recepient, const F& func, Qt::ConnectionType type = Qt::QueuedConnection)
: func_(func)
{
moveToThread(recepient->thread());
QMetaObject::invokeMethod(this, "fire", type);
}
virtual ~Package() {};
private:
Q_SLOT void fire()
{
if (func_) func_();
deleteLater();
}
std::function<void()> func_;
};
Комментарии
Отправить комментарий