QTimer::timeout не срабатывает

Я пытаюсь создать событие, которое запускается каждые n секунд в моем работнике Singleton. Соединение сигнал/слот (при этом сигнал является тайм-аутом QTimer, а слот является лямбда-функцией, которая вызывает другой класс Singleton) не работает. Вызов подключения выполнен успешно, таймер активен, и я не получаю жалоб на QTimer на консоли. Если я попытаюсь распечатать оставшееся время QTimer, он прочитает -1. На всю жизнь я не могу понять, почему «тайм-аут» никогда не печатается (указывая, что событие запускается). Любая помощь будет принята с благодарностью. Для простоты можно предположить, что OtherSingleton имеет такую ​​же структуру. Я также должен отметить, что этот объект класса Singleton работает внутри QThread.

Синглтон.ч:

#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_;

};

Синглтон.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_;

Главное окно.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();
}

основной.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();
}

person Chase    schedule 22.02.2019    source источник
comment
QTimer не может работать, если нет основного цикла, обрабатывающего события. Последнее, например. часть QApplication::exec(). Вы называете это?   -  person Scheff's Cat    schedule 22.02.2019
comment
bytes_timer_.moveToThread(QThread::currentThread()); меня пугает. Для чего это? (Таймеры и потоки - это просто еще одна тема, которая хороша для проблем...) Если таймер должен запускаться из потока (я не уверен, зачем это нужно), то этому потоку также нужен цикл обработки событий. Я нашел вики-статью Qt об этом: Threads Events QObjects.   -  person Scheff's Cat    schedule 22.02.2019
comment
Я нашел похожий вопрос, который может быть полезен: SO: запуск QTimer в QThread.   -  person Scheff's Cat    schedule 22.02.2019
comment
Было бы неплохо превратить открытый код в минимально воспроизводимый пример. Это было бы легче прокомментировать (точно), не говоря уже о том, что было бы легче воспроизвести вашу проблему.   -  person Scheff's Cat    schedule 22.02.2019
comment
@Scheff Я отредактировал ответ, пожалуйста, проверьте сейчас. На самом деле у меня возникли проблемы с отображением окна графического интерфейса с использованием моего MCVE, потому что get_instance() блокируется при инициализации s.   -  person Chase    schedule 23.02.2019
comment
Почему вы хотите запустить таймер в другом потоке? Было бы совершенно нормально запустить его в потоке графического интерфейса. (Если у вас есть веские причины, по которым QTimer должен быть в треде, стоило бы упомянуть об этом в вопросе.)   -  person Scheff's Cat    schedule 23.02.2019


Ответы (2)


У меня почему-то такое ощущение, что OP собирается перепроектировать то, что на самом деле может быть довольно простым.

Я сделал MCVE, чтобы продемонстрировать это.

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

Соберите и запустите:

$ 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

Снимок testQTimerStartStop (анимированный)

Перемещение QTimer в другой поток добавит много накладных расходов. Любой доступ к QTimer имел

  • произойти перед запуском потока или
  • быть защищенным мьютексом или
  • сделать через сигналы (с Qt::QueueConnection).

На мгновение я подумал о том, чтобы адаптировать свой образец соответственно, но вскоре осознал необходимость усилий и остановился. ИМХО, я бы не рекомендовал это, если нет веских причин для этого.

Пожалуйста, имейте в виду, что: приложение, которое находится под большой нагрузкой, которая вызывает значительные задержки выдачи сигнала тайм-аута, вероятно, также не в состоянии обрабатывать события тайм-аута, которые вовремя генерируются другим потоком.

person Scheff's Cat    schedule 23.02.2019
comment
После изменения моего одноэлементного класса у меня все заработало. В отличие от перемещения всего объекта в новый поток из графического интерфейса, теперь синглтон просто создает новый поток для того, что функция start_client() должна выполняться в фоновом режиме. Теперь QTimer работает в том же потоке, что и GUI. Спасибо вам за ваши предложения. - person Chase; 23.02.2019

как указано на официальном сайте, вам нужно запустить таймер

bytes_timer_.start();

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

person ΦXocę 웃 Пepeúpa ツ    schedule 22.02.2019
comment
Разве это не то, что ОП сделал Singleton::start_client()? Без MCVE трудно сказать, сделал ли (или нет) OP это. - person Scheff's Cat; 22.02.2019
comment
Как отражено в обновленном ответе, bytes_timer_.start() вызывается через start_client(), который вызывается в потоке графического интерфейса при нажатии кнопки. - person Chase; 23.02.2019
comment
Спасибо за это, это решило мою проблему. - person Kingsley; 08.06.2021