8

I need a way to run an update function of my own in the main thread. I couldn't find a signal that would tick me every time the main loop runs.

Am I doing this wrong ? Is it a Qt thing to force user code to run in threads if we want to run something in a loop?

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
awpsoleet
  • 257
  • 1
  • 3
  • 11

3 Answers3

18
QTimer::singleShot(0, []{/* your code here */});

That's about it, really. Using a 0ms timer means your code will run on the next event loop iteration. If you want to make sure the code won't run if a certain object doesn't exist anymore, provide a context object:

QTimer::singleShot(0, contextObj, []{/* your code here */});

This is well documented.

I used a lambda here just for the example. Obviously you can provide a slot function instead if the code is long.

If you want your code to be executed repeatedly on every event loop iteration instead of just once, then use a normal QTimer that is not in single-shot mode:

auto timer = new QTimer(parent);
connect(timer, &QTimer::timeout, contextObj, []{/* your code here */});
timer->start();

(Note: the interval is 0ms by default if you don't set it, so QTimer::timeout() is emitted every time events have finished processing.)

Here's where this behavior is documented.

And it goes without saying that if the code that is executed takes too long to complete, your GUI is going to freeze during execution.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • So if I understand correctly I will have to call singleShot every time my code runs if I want to have it run in every iteration ? This seems awfully inefficient. – awpsoleet May 03 '16 at 09:29
  • 1
    @awpsoleet You can use a normal QTimer in that case. I updated the answer. – Nikos C. May 03 '16 at 11:04
  • It is worth noting that this code sadly doesn't work from threads not started via QThread: "Timers can only be used with threads started with QThread" – LubosD Jan 08 '18 at 10:06
  • Note that the repeated timeout version will make the event loop run back to back in intervals of < 1ms (on my machine with Qt 6.2), i.e. it will run as fast as possible. Be aware of this and maybe use a timeout of something like 16ms. The documentation also states that the 0ms timeout is a legacy thing for running work "async" in a single threaded environment and that the timer should be stopped once this work is done. – ma-hnln Aug 30 '22 at 08:29
3

Alternatively, if you want to execute your code every time the event loop runs you can use the slot method invocation via the queued connection:

class EvtLoopTicker : public QObject
{
    Q_OBJECT
public:

    EvtLoopTicker(QObject *pParent = nullptr)
        : QObject(pParent)
    {}

public slots:

    void launch()
    {
        tickNext();
    }

private slots:

    void tick()
    {
        qDebug() << "tick";

        // Continue ticking
        tickNext();
    }

private:

    void tickNext()
    {
        // Trigger the tick() invokation when the event loop runs next time
        QMetaObject::invokeMethod(this, "tick", Qt::QueuedConnection);
    }

};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    EvtLoopTicker ticker;
    ticker.launch();

    return a.exec();
}
Archie
  • 2,644
  • 21
  • 21
2

Yet another way would be to override:

bool QCoreApplication::event(QEvent *e)

register a user QEvent and post the event to QCoreApplicatio::instance(). Obviously, the QTimer approach is superior, but this one will work even if the issuing thread was not created by Qt (QThread).

example:

class MainThreadEvent: public QEvent
{
  std::function<void()> f_;

public:
  template <typename F>
  explicit MainThreadEvent(F&& f) :
    QEvent(event_type()),
    f_(std::forward<F>(f))
  {
  }

  void invoke()
  {
    f_();
  }

  static auto event_type()
  {
    static int et{-1};

    return QEvent::Type(-1 == et ? et = registerEventType() : et);
  }

  template <typename F>
  static void post(F&& f)
  {
    auto const app(QCoreApplication::instance());

    app->postEvent(app, new MainThreadEvent(std::forward<F>(f)));
  }
};

class UserApplication: public QApplication
{
  using QApplication::QApplication;

  bool event(QEvent* const e) final
  {
    if (MainThreadEvent::event_type() == e->type())
    {
      return static_cast<MainThreadEvent*>(e)->invoke(), true;
    }
    else
    {
      return QApplication::event(e);
    }
  }
};

EDIT: example for an update() from an arbitrary thread:

MainThreadEvent::post(
  [p = QPointer(this)]()
  {
    if (p)
    {
      p->update();
    }
  }
);
user1095108
  • 14,119
  • 9
  • 58
  • 116