문제

I'm working on an application developed with Qt 4.6.

I want to create a custom timer that counts in a separate thread. However, I want this timer to be able to send signals to the main thread.

I subclassed QThread but it doesn't seem to work.

Here is Timer.h:

#ifndef TIMER_H
#define TIMER_H

#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QTimer>

class Timer : public QThread
{
    Q_OBJECT
public:
    explicit Timer(QObject *parent = 0);
    ~Timer();

    // true if the timer is active
    bool isCounting();

    // start the timer with a number of seconds
    void startCounting(int value = 300);
    void stopCounting();

    // the number of seconds to reach
    int maximum();

    // the current value of the timer
    int value();

    // elapsed time since the timer has started
    int elapsedTime();

signals:
    // sent when the timer finishes to count
    void timeout();
    // an event is emited at each second when the timer is active
    void top(int remainingSeconds);

protected:
    // launch the thread
    //virtual void run();

private slots:
    // decrements the remaining time at each second and emits top()
    void timerEvent();

private:
    QTimer* _timer;
    // remaining time
    int _left;
    // number of seconds at timer startup
    int _maximum;
};

#endif // TIMER_H

And Timer.cpp:

#include "Timer.h"

Timer::Timer(QObject *parent) :
    QThread(parent)
{
    _timer = new QTimer(this);
    _maximum = 0;
    _left = 0;
    connect(_timer, SIGNAL(timeout()), this, SLOT(timerEvent()));
}

Timer::~Timer()
{
    delete _timer;
}

bool Timer::isCounting()
{
    // test if timer still active
    return _timer->isActive();
}

void Timer::startCounting(int value)
{
    qDebug() << QString("Start timer for %1 secs").arg(QString::number(value));
    if(_left != 0 || _timer->isActive())
    {
         _timer->stop();
    }

    _maximum = value;
    _left = value;

    // emit the first top
    emit top(_left);

    // start the timer: 1000 msecs
    _timer->start(1000);

    // start the thread
    start();
}

void Timer::stopCounting()
{
    qDebug() << QString("Stopping timer at %1 secs => %2 secs remaining.").arg(QString::number(elapsedTime()), QString::number(_left));
    // stop timer
    _timer->stop();
    _left = 0;
    _maximum = 0;
    // kill thread
    terminate();
}

int Timer::maximum()
{
    return _maximum;
}

int Timer::value()
{
    return _left;
}

void Timer::timerEvent()
{
    qDebug() << "Timer event";
    if(--_left == 0)
    {
        // stop timer
        _timer->stop();
        // emit end of timer
        emit timeout();
        // stop thread
        terminate();
    }
    else
    {
        // emit a signal at each second
        emit top(_left);
    }
}

int Timer::elapsedTime()
{
    return (_maximum - _left);
}

EDIT

I realized the object I tried to move to another thread was actually a singleton. It could lead to a problem (see here).

도움이 되었습니까?

해결책

You don't need to subclass QThread in this particular case. And in general, abstain from subclassing QThread unless you are sure it is what you need.

Here is a quick example how to setup a worker and timer in a thread and launch it:

the worker class:

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = 0) : QObject(parent) {}

signals:
    void doSomething();

public slots:
    void trigger() {
        emit doSomething();
    }
};

main.cpp

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

    MainThreadObject o;

    QThread *thread = new QThread;
    Worker w;
    QTimer timer;
    timer.setInterval(1000);

    timer.moveToThread(thread);
    w.moveToThread(thread);

    QObject::connect(thread, SIGNAL(started()), &timer, SLOT(start()));
    QObject::connect(&w, SIGNAL(doSomething()), &o, SLOT(doSomething()));
    QObject::connect(&timer, SIGNAL(timeout()), &w, SLOT(trigger()));

    thread->start();

    return a.exec();
}

So, we have the MainThreadObject which represents a QObject derived living in the main thread. We create the timer and Worker object, which is just used to wrap a signal and slot to avoid the need of subclassing QThread. The timer is setup and it and the worker are moved to the new thread, the thread started() signal is connected to the timer start() slot, the worker doSomething() signal is connected to the main thread object doSomething() slot, and finally the timer timeout() signal is connected to the worker trigger() slot. Then the thread is started which initiates the entire chain in the event loop.

As a result, the MainThreadObject::doSomething() is called every second, with the signal emitted from the secondary thread.

다른 팁

Try

QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection); //timer->start()

if you want to start timer immediately

Or

QMetaObject::invokeMethod(&timer, "start", Qt::QueuedConnection , Q_ARG(int, 1000 )); //timer->start(200)

if you want to start timer after 1000s

In the Non-GUI thread (QThread or Pthread Callback)

First, if you subclass from QThread, you have to implement run() method, if not, there is no point of doing that, you can inherit from QObject instead.

Second, your QTimer has to reside in a thread that runs an event loop. Without an event loop no Qt queued signals can be transmitted. You can launch an event loop by calling exec() in thread's run method:

void Timer::run() {
    exec();
}

Probable reason can be, your timer object is not in a thread with event loop. Event loop is required to trigger the signals.

However, I would suggest that you should not go with this approach. Timers use different mechanism on different platform and your code might not behave as expected on different platform.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top