title: Many active QTimers timekeeping causing GUI lag
body: <p>I have created a simple app for monitoring the connected devices. This app shows the connection status of 25 client devices. </p>
  
  <p>This app implements a TCP server and listens at port 7777 and as many as 25 clients can be connected to this application. If no data is received from a client for 30 seconds, the app marks the device as "Offline". </p>
  
  <p>For this purpose, QTimer for each connected device is started for 30 sec when some client connects and the payload is received. Each timer is connected to a common SLOT <strong>refreshOfflineDevices()</strong> Soon as any timer timeout occurs, <strong>refreshOfflineDevices()</strong> is called and the non-running timers corresponding to the device are marked as "Offline" in the GUI. </p>
  
  <p>The app works fine and the GUI is updated instantly when the connected device count is not more than 4 or 5. As the connected devices rise, (greater than 8 or 9) the lag in the GUI update becomes obvious.</p>
  
  <p>After some desk research, I assume that the parallel timers would need to be moved to a thread to avoid GUI lags. For that, I created a <strong>CyclicWorker</strong> class for separating the QTimer but not sure how this will work in this case</p>
  
  <p>I need help with moving and managing all timekeeping events to a thread. Also, I need advise on my assumption of GUI lag correctness</p>
  
  <p>my app GUI</p>
  
  <p><img src="https://i.stack.imgur.com/WyGpy.png" alt=""></p>
  
  <p><strong>monitor.h</strong></p>
  
  <pre><code>#ifndef CENTRALMONITOR_H
  #define CENTRALMONITOR_H
  
  #include &lt;QtCore&gt;
  #include &lt;QMainWindow&gt;
  #include "ui_device_display.h"
  #include "tcp_server.h"
  #include "cyclic_worker.h"
  
  #define MAX_DEVICES             (25)
  #define DEVICE_KEEP_ALIVE_MS    (30*1000)   // keeps track of the connection before marking "Offline"
  
  namespace Ui
  {
  class MainWindow;
  }
  
  class MainWindow : public QMainWindow
  {
      Q_OBJECT
  
  private:
      Ui::MainWindow *ui;
      TCPServer *ptr_server = nullptr;
      QTimer *ptr_deviceTimer[MAX_DEVICES] = {nullptr};
  
      void GUI_update(const int device_number, const QString device_status);
  
      CyclicWorker timerThread;
  
  public:
      explicit MainWindow(QWidget *parent = 0);
      ~MainWindow();
      UIDeviceDisplay *ptr_devices[MAX_DEVICES] = {nullptr};
  
  public slots:
      void parseJSON(QString response);
      void refreshOfflineDevices();
  };
  
  #endif // CENTRALMONITOR_H
  </code></pre>
  
  <p><strong>monitor.cpp</strong></p>
  
  <pre><code>#include "monitor.h"
  #include "ui_monitor.h"
  
  MainWindow::MainWindow(QWidget *parent) :
      QMainWindow(parent),
      ui(new Ui::MainWindow)
  {
      system("clear");
      ui-&gt;setupUi(this);
  
      // MyServer instance (TCPServer contains TCPClient instance).
      ptr_server = new TCPServer();
      connect(ptr_server, SIGNAL(uiPayloadReady(QString)), this, SLOT(parseJSON(QString)));
  
      // draw (MAX_DEVICES) (initialize as offline)
      for(int i=0 ; i&lt;5 ; i++)        // 5 rows
      {
          for(int j=0 ; j&lt;5 ; j++)    // 5 columns
          {
              ptr_devices[i*5 + j] = new UIDeviceDisplay(this, j, i);
              ptr_devices[i*5 + j]-&gt;show();
  
              QString text_device_number = QString("").append(QString::number( i*5 + j + 1) );
              ptr_devices[i*5 + j]-&gt;ptr_label_device_number-&gt;setText(text_device_number);
          }
      }
  
      // connect keep alive timers to use later
      for(int device_idx=0; device_idx&lt;MAX_DEVICES; device_idx++)
      {
          ptr_deviceTimer[device_idx] = new QTimer();
          connect(ptr_deviceTimer[device_idx], SIGNAL(timeout()), this, SLOT(refreshOfflineDevices()));
          this-&gt;ptr_deviceTimer[device_idx]-&gt;setSingleShot(true);
          ptr_deviceTimer[device_idx]-&gt;setTimerType(Qt::PreciseTimer);
      }
  
      timerThread.start();
      timerThread.threadFlag = 1;
  }
  
  MainWindow::~MainWindow()
  {
      delete ui;
  }
  
  /*
      @brief   This slot is emitted by ptr_socket readReady() signal by the TCP client handler
  
      @param   The received payload for updating GUI
  */
  void MainWindow::parseJSON(const QString response)
  {
      const QJsonDocument jsonDocument = QJsonDocument::fromJson(response.toUtf8());
      const QJsonObject jsonObjectRecords = jsonDocument.object();
      const int device_number = jsonObjectRecords.value("device_number").toInt();
      const QString device_status = jsonObjectRecords.value("device_status").toString();
  
      // start time keeper for current device.
      ptr_deviceTimer[device_number-1]-&gt;start(DEVICE_KEEP_ALIVE_MS);
      GUI_update(device_number, device_status);
  }
  
  /*
      @brief   This method updates the GUI with provided params.
  
      @param   GUI update params
  */
  void MainWindow::GUI_update(const int device_number, const QString device_status)
  {
      const int device_idx = device_number-1;
  
      // update device label.
      ptr_devices[device_idx]-&gt;ptr_label_device_status-&gt;setText(device_status);
  
      // refresh online devices label.
      int onlineCount =0;
      for(int device_idx=0; device_idx&lt;MAX_DEVICES; device_idx++)
      {
          if( ptr_deviceTimer[device_idx]-&gt;isActive() )
              onlineCount++;
      }    
      ui-&gt;label_online_devices-&gt;setText(QString("Online devices: %1").arg(onlineCount));
  }
  
  /*
      @brief   This method is called upon every device_timer expiration. It updates GUI for all the devices that are offline
  */
  void MainWindow::refreshOfflineDevices()
  {
      for(int device_number=1; device_number&lt;=MAX_DEVICES; device_number++)
      {
          // if device timer is not running, the device is offline
          if( !ptr_deviceTimer[device_number-1]-&gt;isActive() )
          {
              GUI_update(device_number, "Offline");
          }
      }
  }
  </code></pre>
  
  <p><strong>cyclic_worker.h</strong></p>
  
  <pre><code>#include &lt;QDebug&gt;
  #include &lt;QThread&gt;
  
  class CyclicWorker : public QThread
  {
      Q_OBJECT
  public:
      bool threadFlag;                        // variable used to control thread execution
      CyclicWorker();
      void run();
      void quit();
  
  private:
  
  };
  </code></pre>
  
  <p><strong>cyclic_worker.cpp</strong></p>
  
  <pre><code>#include "cyclic_worker.h"
  
  CyclicWorker::CyclicWorker()
  {
      qDebug() &lt;&lt; "\nCyclicWorker object created";
      threadFlag = false;
  }
  
  /*----------------------------------------------------------------------------
   * void run()
   * 
   * Return value  : none
   * 
   * Description : this function runs after thread start
   *----------------------------------------------------------------------------*/
  void CyclicWorker::run()
  {
      qDebug("Thread invoked . . .");
      while(threadFlag)
      {
      }
  }
  
  /*----------------------------------------------------------------------------
   * void quit()
   * 
   * Return value  : none
   * 
   * Description : this function stops the running thread
   *----------------------------------------------------------------------------*/
  void CyclicWorker::quit()
  {
      qDebug() &lt;&lt; "Thread stopped . . .";
  }
  </code></pre>
  
  <p><strong>ui_device_display.h</strong></p>
  
  <pre><code>#ifndef UI_DEVICE_DISPLAY_H
  #define UI_DEVICE_DISPLAY_H
  
  #include &lt;QtWidgets&gt;
  
  #define X_PADDING           (30)        // this is the base container widget co-ordinates
  #define Y_PADDING           (110)
  
  class UIDeviceDisplay : public QFrame
  {
      Q_OBJECT
  public:
      UIDeviceDisplay(QWidget *parent = nullptr, int x=0, int y=0);
      QLabel *ptr_label_device_number = nullptr;
      QLabel *ptr_label_device_status = nullptr;
  
      static const int frameWidth = 240;
      static const int frameHeight = 190;
      static const int deviceLabelWidth = 70;
      static const int deviceLabelHeight = 50;
      static const int statusLabelWidth = 150;
      static const int statusLabelHeight = 30;
  };
  
  #endif
  </code></pre>
  
  <p><strong>ui_device_display.cpp</strong></p>
  
  <pre><code>#include "ui_device_display.h"
  
  UIDeviceDisplay::UIDeviceDisplay(QWidget *parent, int x, int y) : QFrame(parent)
  {
      //QFrame containing all the elements.
      this-&gt;setGeometry(QRect( X_PADDING + frameWidth*x, Y_PADDING + frameHeight*y, frameWidth, frameHeight));
      this-&gt;hide();
  
      //QLabel for bed number.
      ptr_label_device_number = new QLabel(this);
      ptr_label_device_number-&gt;setGeometry(QRect((frameWidth/2)-(deviceLabelWidth/2), 20, deviceLabelWidth, deviceLabelHeight));
      ptr_label_device_number-&gt;setAlignment(Qt::AlignCenter);
  
      //QLabel that displays the device status.
      ptr_label_device_status = new QLabel(this);
      ptr_label_device_status-&gt;setText("Offline");
      ptr_label_device_status-&gt;setGeometry(QRect(45, 90, statusLabelWidth, statusLabelHeight));
      ptr_label_device_status-&gt;setAlignment(Qt::AlignCenter);
  }
  </code></pre>
  
  <p><strong>tcp_server.h</strong></p>
  
  <pre><code>#ifndef TCP_SERVER_H
  #define TCP_SERVER_H
  
  #include &lt;QTcpServer&gt;
  #include &lt;QTcpSocket&gt;
  #include &lt;QAbstractSocket&gt;
  #include "tcp_client_handler.h"
  
  class TCPServer : public QTcpServer
  {
      Q_OBJECT
  public:
      explicit TCPServer(QObject *parent=0);
  
  protected:
      void incomingConnection(int handle);
  
  signals:
      void uiPayloadReady(QString uiPayload);
  
  public slots:
      void payloadReady(QString payload);
  };
  
  #endif
  </code></pre>
  
  <p><strong>tcp_server.cpp</strong></p>
  
  <pre><code>#include "tcp_server.h"
  
  TCPServer::TCPServer(QObject *parent) :
      QTcpServer(parent)
  {
      if(listen(QHostAddress::Any,7777))
          qDebug("DEBUG: Server listening at 7777");
      else
          qDebug("DEBUG: Could not start server");
  }
  
  void TCPServer::incomingConnection(int handle)
  {
      TCPClientHandler *ptr_client = new TCPClientHandler(this);
      ptr_client-&gt;SetSocket(handle);
      connect(ptr_client, SIGNAL(payloadReady(QString)), this, SLOT(payloadReady(QString)));
  }
  
  void TCPServer::payloadReady(QString payload)
  {
      emit uiPayloadReady(payload);
  }
  </code></pre>
  
  <p><strong>tcp_client_handler.h</strong></p>
  
  <pre><code>#ifndef TCP_CLIENT_HANDLER_H
  #define TCP_CLIENT_HANDLER_H
  
  #include &lt;QObject&gt;
  #include &lt;QTcpSocket&gt;
  #include &lt;QDebug&gt;
  
  class TCPClientHandler : public QObject
  {
      Q_OBJECT
  public:
      explicit TCPClientHandler(QObject *parent = nullptr);
      void SetSocket(int Descriptor);
      QTcpSocket *ptr_socket = nullptr;
  
  signals:
      void payloadReady(QString payload);
  
  public slots:
      void connected();
      void disconnected();
      void readReady();
  
  private:
  };
  
  #endif
  </code></pre>
  
  <p><strong>tcp_client_handler.cpp</strong></p>
  
  <pre><code>#include "tcp_client_handler.h"
  
  TCPClientHandler::TCPClientHandler(QObject *parent) : QObject(parent)
  {
  }
  
  void TCPClientHandler::SetSocket(int Descriptor)
  {
      ptr_socket = new QTcpSocket(this);
  
      connect(ptr_socket, SIGNAL(connected()), this, SLOT(connected()));
      connect(ptr_socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
      connect(ptr_socket, SIGNAL(readyRead()), this, SLOT(readReady()));
  
      ptr_socket-&gt;setSocketDescriptor(Descriptor);
  }
  
  void TCPClientHandler::connected()
  {
      //qDebug("DEBUG: client connect event");
  }
  
  void TCPClientHandler::disconnected()
  {
      //qDebug("DEBUG: client disconnect event");
  }
  
  void TCPClientHandler::readReady()
  {
      const QByteArray byteArrayResponse = ptr_socket-&gt;readAll();
      const QString stringResponse = QString(byteArrayResponse);
      //qDebug() &lt;&lt; "DEBUG: " &lt;&lt; stringResponse;
  
      emit payloadReady(stringResponse);
  }
  </code></pre>
  
  <p><strong>main.cpp</strong></p>
  
  <pre><code>#include "monitor.h"
  
  #include &lt;QApplication&gt;
  
  int main(int argc, char *argv[])
  {
      QApplication a(argc, argv);
      MainWindow w;
      w.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
      w.show();
  
      return a.exec();
  }
  </code></pre>