Invoke slot asynchronously without connecting to it using clear line of code
Asked Answered
F

1

2

I have encountered quite freaky bug - QAction::trigger caused blocking dialog to appear, which caused my server which called trigger to go stuck (eg. not able to process socket signals until dialog was closed).

I figured out a workaround. I connect signal void triggerWorkaround() to slot QAction::trigger using Qt::QueuedConnection and I emit it:

QObject::connect(this, &HackClass::triggerWorkaround, targetAction_.data(), &QAction::trigger, Qt::QueuedConnection);
emit triggerWorkaround();
QObject::disconnect(this, nullptr, targetAction_.data(), nullptr);

But that's three lines of confusing code. Is there a non-confusing method to do this? I have found QMetaObject::invokeMethod, but frankly, that's 10 times more confusing than my current solution. Also, I don't want to ever use method name as string!

Frigging answered 27/1, 2017 at 15:57 Comment(1)
Maybe make a separate thread for the server's event loop.Radish
Z
2

You can separate that into a function QueuedInvoke like this:

//overload for methods/slots
//the slot gets invoked in the thread where the QObject lives
template <typename Object, typename T>
void QueuedInvoke(Object* object, T (Object::* f)()){
    QObject signalSource;
    QObject::connect(&signalSource, &QObject::destroyed,
                     object, f, Qt::QueuedConnection);
}
//overload for functors
//the functor gets invoked in the thread where the contextObject lives
//or in the current thread if no contextObject is provided
template <typename Func>
void QueuedInvoke(Func&& f, QObject* contextObject = QAbstractEventDispatcher::instance()){
    QObject signalSource;
    QObject::connect(&signalSource, &QObject::destroyed, 
                     contextObject, std::forward<Func>(f), Qt::QueuedConnection);
}

This will leverage the destroyed() signal emitted from the temporary QObject to post a queued event into the event loop. The slot/functor is actually invoked when the event loop processes that event.

So, Instead of the 3 lines you posted, You can use the above function like this:

QueuedInvoke(targetAction_.data(), &QAction::trigger);

My answer is based on this great answer about executing a functor in a given QThread. You can refer to it for more details.

Zestful answered 28/1, 2017 at 13:52 Comment(5)
@TomášZato , In order to run the lambda in a specific thread, You can provide a context QObject.Zestful
Would it be possible to enable parameters using C++11 variadic templates?Trilbi
@Silicomancer, Well, You can definitely have something like this. But I think that using lambdas and capturing the variables you need there, makes the capturing method (i.e. value or reference) clearer when you are reading the code later. what do you think? You may also want to take a look at an alternative approach in this answer.Zestful
@Silicomancer, but when using this approach, one can leverage blocking queued connections to wait for the function until it finishes executing in the other thread and possibly get its return value in the current thread, something like this.Zestful
This answer is great, unfortunately doesn't work in Qt 4.8 (no functors in connections :( )Akerboom

© 2022 - 2024 — McMap. All rights reserved.