Domanda

I have written a thread in Qt that does lot of things (calculations, data sampling, ...).
This thread has to be run in an interval of 1000ms.
The allowed error with the timer is about 5ms.
I have changed the priority of the thread to QThread::HighPriority but the thread runs in interval about to 1060ms-1100ms.
How can I make the interval more precise? (I have subclassed QThread and used msleep(interval) in the run() method).

È stato utile?

Soluzione

You have coded your thread's run() method essentially as:

void MyThread::run() {
  forever {
    doSomething();
    msleep(1000);
  }
}

There are several problems:

  1. doSomething() doesn't take zero amount of time. At the minimum, you'd need to time how long doSomething() takes and sleep that much shorter than 1000ms.

  2. Both doSomething() and msleep() can take a variable amount of time, since your thread is never guaranteed not to be preempted, nor is it guaranteed to immediately start running once it's made runnable by the sleep expiring. Thus you need to keep track of time absolutely, not relatively to the start of doSomething().

  3. You're using a generic sleep function without possibly leveraging better APIs that the underlying platform might offer.

A reasonably correct way to go about it would be expressed using this pseudocode:

const qint64 kInterval = 1000;
qint64 mtime = QDateTime::currentMSecsSinceEpoch();
forever {
  doSomething();
  mtime += kInterval;
  qint64 sleepFor = mtime - QDateTime::currentMSecsSinceEpoch();
  if (sleepFor < 0) {
    // We got preempted for too long - for all we know, the system could
    // have even gotten suspended (lid close on a laptop).
    // Note: We should avoid the implementation-defined behavior of 
    // modulus (%) for negative values.
    sleepFor = kInterval - ((-sleepFor) % kInterval);
  }
  OS_Precise_Wait_ms(sleepFor); // use the appropriate API on given platform
}

As luck would have it, Qt provides an API that does all this for you: the timers. They are a source of reasonably behaved periodic "ticks". Most naive re-implementations of this functionality are likely to get it wrong in one way or another, since it's not as simple as it looks.

Here is how you can reorganize the code:

class Worker : public QObject {
  QBasicTimer m_timer;
  void doSomething() {
    // do the work
  }
  void timerEvent(QTimerEvent * ev) {
    if (ev->timerId() != m_timer.timerId()) {
        QObject::timerEvent(ev);
        return;
    }
    doSomething();
  }
public:
  Worker(QObject * parent = 0) : QObject(parent) {
    m_timer.start(1000, Qt::PreciseTimer, this);
  }
};

int main(int argc, char ** argv) {
  QCoreApplication app(argc, argv);
  Worker worker;
  QThread workerThread;
  worker.moveToThread(workerThread);
  workerThread.start(QThread::HighPriority);

  // Example of how to terminate the application after 10 seconds
  // Requires Qt 5 and a C++11 compiler.
  QTimer timer;
  QObject::connect(&timer, &QTimer::timeout, [&](){
    workerThread.quit();
    workerThread.wait();
    app.quit();
  });
  timer.setTimerType(Qt::VeryCoarseTimer);        
  timer.setSingleShot(true);
  timer.start(10000);

  return app.exec();
}

Altri suggerimenti

From the docs of QTimer class:

Accuracy and Timer Resolution

The accuracy of timers depends on the underlying operating system and hardware. Most platforms support a resolution of 1 millisecond, though the accuracy of the timer will not equal this resolution in many real-world situations.

The accuracy also depends on the timer type. For Qt::PreciseTimer, QTimer will try to keep the accurance at 1 millisecond. Precise timers will also never time out earlier than expected.

For Qt::CoarseTimer and Qt::VeryCoarseTimer types, QTimer may wake up earlier than expected, within the margins for those types: 5% of the interval for Qt::CoarseTimer and 500 ms for Qt::VeryCoarseTimer.

All timer types may time out later than expected if the system is busy or unable to provide the requested accuracy. In such a case of timeout overrun, Qt will emit activated() only once, even if multiple timeouts have expired, and then will resume the original interval.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top