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!

Community
  • 1
  • 1
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778

1 Answers1

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.

Community
  • 1
  • 1
Mike
  • 8,055
  • 1
  • 30
  • 44
  • @TomášZato , In order to run the lambda in a specific thread, You can provide a context `QObject`. – Mike Mar 02 '17 at 22:19
  • Would it be possible to enable parameters using C++11 variadic templates? – Silicomancer Jun 28 '17 at 20:57
  • @Silicomancer, Well, You can definitely have something like [this](https://pastebin.com/9xbM1wS2). 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](https://stackoverflow.com/a/42566867/2666212). – Mike Jun 29 '17 at 11:57
  • @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](https://gist.github.com/micjabbour/b8b3cfd1b566a041b8ad279a139e8362#file-qthreadutils-h-L26). – Mike Jun 29 '17 at 12:06
  • This answer is great, unfortunately doesn't work in Qt 4.8 (no functors in connections :( ) – Llopeth Jun 05 '18 at 14:50