Страницы

Поиск по вопросам

понедельник, 17 июня 2019 г.

Qt Как выполнить метод в главном потоке?

У меня есть приложение которое выполняет некоторую работу в отдельном потоке:
class Worker : public QRunnable{ public: void run(){ //Какие-то действия } };
class MainWindow : public QWidget{ Q_OBJECT public: MainWindow(QWidget *parent = 0): QWidget(parent) { QThreadPool::globalInstance()->start(new Worker); } };
Мне необходимо вызвать в методе run функцию из другой библиотеки, которая ничего не знает о потоках Qt и не дружит с ними ни под каким предлогом:
void run(){ //... iHateQtThreads(arg1, arg2, arg3); //Ошибка //... }
Нужно каким-то способом вызвать эту функцию в главном потоке. Какой есть красивый способ это сделать?
PS: Функция вызывается многократно с определенным интервалом. Если использовать синалы/слоты с подключением Qt::BlockingQueuedConnection или Qt::QueuedConnection, то интервал нарушается. Сигналы сначала накапливаются, а потом выстреливают все разом.


Ответ

Вызов функции через очередь посредством сигнал/слотов или QMetaObject::invokeMethod() - это по сути декларация о намерении. Лишь когда контекст выполнения целевого потока перейдёт к перебору очереди событий, то только тогда запланированное действие (вызов функции) будет произведено.
Соответственно, если целевой поток занят какой-либо задачей и относительно редко просматривает очередь событий, то декларации на вызов функции будут накапливаться. Как только контекст выполнения целевого потока освободится и наконец перейдёт к очереди событий, то разом будут произведены все ранее запланированные вызовы функции. Чтобы этого избежать, необходимо минимизировать время нахождения контекста выполнения целевого потока во вне его очереди событий. Проще говоря, целевой поток вообще ничего не должен делать и должен находиться в постоянном ожидании, либо его работа должна быть сведена к выполнению минимума задач.
В основе вызова функции через очередь посредством сигнал/слотов или QMetaObject::invokeMethod() лежит создание и отправка объекта события QMetaCallEvent. В целевой поток объект события отправляется посредством метода QCoreApplication::postEvent(), у которого в свою очередь имеется аргумент приоритетности выполнения со значением по умолчанию Qt::NormalEventPriority. Чем выше приоритет события, тем ранее оно будет выполнено обработчиком в очереди событий. Разумеется при условии, что контекст выполнения целевого потока не отвлекается на длительное время с целью выполнения посторонних задач.
К сожалению, в Qt не предусмотрена возможность указывать приоритет выполнения событий, создаваемых посредством вызова сигналов. Также, не предусмотрено это и при вызове QMetaObject::invokeMethod(). Однако никто не мешает организовать свою реализацию той же техники, но на основе собственного типа событий.
Например, произвольное событие:
Q_GLOBAL_STATIC_WITH_ARGS(int, _g_event_type , (QEvent::registerEventType()))
class IHateQtThreadsEvent : public QEvent { public: static QEvent::Type eventType() { return static_cast(*_g_event_type); }
IHateQtThreadsEvent() : QEvent(IHateQtThreadsEvent::eventType()) {} };
... можно отправлять из потока-источника обычным в Qt образом, но с указанием повышенного уровня приоритетности:
void run() { ... QCoreApplication::postEvent(receiver_obj , new IHateQtThreadsEvent() , Qt::HighEventPriority); ... }
Указатель "receiver_obj" - это произвольный наследник QObject, "живущий" в целевом потоке. Для него останется лишь добавить пользовательский обработчик событий:
class IHateQtThreadsObject : public QObject { Q_OBJECT
protected: virtual void customEvent(QEvent *event) { if(event->type() == IHateQtThreadsEvent::eventType()) { // Аргументы для вызова функции могут быть переданы // в объекте события. iHateQtThreads(arg1, arg2, arg3); } } };

Комментариев нет:

Отправить комментарий