2

I am trying to create an event that gets fired off every n seconds in my Singleton worker. A signal/slot connection (with the signal being the QTimer timing out and the slot being a lambda function which makes a call to another Singleton class) is not working. The connect call is succeeding, the timer is active, and I get no QTimer complaints on the console. If I try to print QTimer's remaining time it reads -1. For the life of me, I cannot figure out why "timeout" is never being printed (indicating that the event is being triggered). Any help would be greatly appreciated. For simplicity's sake we can assume that OtherSingleton has the same structure. I should also note that this Singleton class object is running inside of a QThread.

Singleton.h:

#include <QObject>
#include <string>
#include <QTimer>
#include <QThread>

class Singleton : public QObject
{
   Q_OBJECT

public:
    static Singleton& get_instance();

    Singleton(Singleton const&) = delete;
    void operator=(Singleton const&) = delete;

    static void stop_client();

    static void start_client();

private:
    Singleton();

    static QTimer bytes_timer_;

};

Singleton.cpp:

#include "Singleton.h"
#include <QDebug>
#include <QTime>
#include <QFile>

Singleton::Singleton()
{
    bytes_timer_.setParent(this);
    bytes_timer_.moveToThread(QThread::currentThread());
    bytes_timer_.setInterval(1000);
    qDebug() << "Timeout success:" << connect(&bytes_timer_, &QTimer::timeout, this, [&]() {
        qDebug() << "timeout";
        // . . .
    }, Qt::DirectConnection);
}

Singleton& Singleton::get_instance() {
    static Singleton instance; 

    return instance;
}

void Singleton::start_client() {
    bytes_timer_.start();
}

void Singleton::stop_client() {
     bytes_timer_.stop();
}

QTimer Singleton::bytes_timer_;

MainWindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include "singleton.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

private:
    QThread thread;
    Singleton *s;
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

MainWindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    s = &Singleton::get_instance();
    s->moveToThread(&thread);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    thread.start();
    s->start_client();
}

main.cpp:

#include "mainwindow.h"
#include <QApplication>
#include <QThread>
#include "singleton.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}
Chase
  • 95
  • 11
  • The `QTimer` cannot work if there is no main loop which processes events. The latter is e.g. part of [`QApplication::exec()`](https://doc.qt.io/qt-5/qapplication.html#exec). Do you call it? – Scheff's Cat Feb 22 '19 at 06:54
  • 1
    The `bytes_timer_.moveToThread(QThread::currentThread());` makes me afraid. What is it good for? (Timers and threads are just another topic which is good for trouble...) If timer shall be fired from a thread (I'm not sure why this should be necessary), then this thread needs an event loop too. I found a Qt wiki article about this: [Threads Events QObjects](https://wiki.qt.io/Threads_Events_QObjects). – Scheff's Cat Feb 22 '19 at 06:57
  • I found a similar question which might be of help: [SO: Starting QTimer In A QThread](https://stackoverflow.com/q/10492480/7478597). – Scheff's Cat Feb 22 '19 at 07:00
  • 3
    It would be nice to turn your exposed code into a [mcve]. This would be easier to comment (precisely) not to mention that it would be easier to reproduce your issue. – Scheff's Cat Feb 22 '19 at 07:02
  • @Scheff I've edited the answer, please check now. I'm actually having trouble getting the GUI window to show using my MCVE because get_instance() is blocking when initializing s. – Chase Feb 23 '19 at 01:38
  • Why do you want to start the timer in a different thread? It would be completely fine to run it in the GUI thread. (If you have good reasons that the `QTimer` must be in a thread it would be worth to mention it in the question.) – Scheff's Cat Feb 23 '19 at 08:22

2 Answers2

1

as documented in the official site, you need to start the timer

bytes_timer_.start();

https://doc.qt.io/qt-5/qtimer.html#start-1

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
  • Isn't this for what OP made `Singleton::start_client()`? Without MCVE, it's hard to say whether (or not) OP did this. – Scheff's Cat Feb 22 '19 at 07:23
  • As reflected in the updated answer, bytes_timer_.start() is called through start_client() which gets called in the GUI thread upon a button click. – Chase Feb 23 '19 at 02:41
0

I somehow have the feeling that OP is about to “over-engineer” what could be actually quite simple.

I made an MCVE to demonstrate this.

testQTimerStartStop.cc:

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  // prepare application
  QApplication app(argc, argv);
  QTimer qTimer;
  qTimer.setInterval(1000);
  // setup GUI
  QWidget qWinMain;
  qWinMain.setWindowTitle(QString::fromUtf8("QTimer Test"));
  QFormLayout qForm;
  QSpinBox qEditTimer;
  qEditTimer.setRange(0, 30);
  qForm.addRow(
    QString::fromUtf8("Count down:"),
    &qEditTimer);
  QPushButton qBtnStart(QString::fromUtf8("Start"));
  qForm.addRow(&qBtnStart);
  QPushButton qBtnStop(QString::fromUtf8("Stop"));
  qForm.addRow(&qBtnStop);
  qWinMain.setLayout(&qForm);
  qWinMain.show();
  // set initial states
  qEditTimer.setValue(10);
  auto updateBtns = [&]() {
    const int count = qEditTimer.value();
    qBtnStart.setEnabled(!qTimer.isActive() && count > 0);
    qBtnStop.setEnabled(qTimer.isActive());
  };
  updateBtns();
  // install signal handlers
  QObject::connect(&qTimer, &QTimer::timeout,
    [&]() {
      qEditTimer.setValue(qEditTimer.value() - 1); // count down
    });
  QObject::connect(&qEditTimer, (void (QSpinBox::*)(int))&QSpinBox::valueChanged,
    [&](int count) {
      if (count <= 0) qTimer.stop();
      updateBtns();
    });
  QObject::connect(&qBtnStart, &QPushButton::clicked,
    [&](bool) { qTimer.start(); updateBtns(); });
  QObject::connect(&qBtnStop, &QPushButton::clicked,
    [&](bool) { qTimer.stop(); updateBtns(); });
  // runtime loop
  return app.exec();
}

testQTimerStartStop.pro:

SOURCES = testQTimerStartStop.cc

QT += widgets

Build and run:

$ qmake-qt5 testQTimerStartStop.pro

$ make && ./testQTimerStartStop
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQTimerStartStop.o testQTimerStartStop.cc
g++  -o testQTimerStartStop.exe testQTimerStartStop.o   -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread 
Qt Version: 5.9.4

Snapshot of testQTimerStartStop (animated)

Moving the QTimer to a different thread would add a lot of overhead. Any access to the QTimer had

  • to happen before starting the thread or
  • to be mutex guarded or
  • to be done through signals (with Qt::QueueConnection).

I considered it for a moment to adapt my sample respectivley but soon realized the necessary effort and stopped. IMHO, I wouldn't recommend this unless there is a good reason to do so.

Please, consider that: An application which is under heavy load which causes significant delays of timeout signal emitting is probably not able as well to handle timeout events which are emitted by another thread in time.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • After modifying my singleton class I've got it working. As opposed to moving the whole object into a new thread from the GUI, now the singleton just makes a new thread for what function start_client() needs to run in the background. The QTimer is now running in the same thread as the GUI. Thank you for your suggestions. – Chase Feb 23 '19 at 20:31