3

I have a Qt gui as QMainWindow which has a QThread that starts a infinite loop of the object of another class.

Here is the brief code: Calculations.h

Class Calculations : public QObject
{
public:
//many functions!
void run();
signals:
void signalResultAvailable();
void signalECUQueryTimePassed();
private:
//many things!
}

and Calculations.cpp

void Calculations::run()
{
    while (1)
    {
        unsigned long loopStart = millis();

        //Heavy calculations per second!

        unsigned long loopEnd = millis();
        if ((loopEnd - loopStart) <= 1000)
        {
                delay(1000 - (loopEnd - LoopStart));
                emit signalResultAvailable;
        }
     }
}

MainWindow.his as follows:

Class MainWindow : public QMainWindow
{

private slots:
    on_button_Pause_clicked();
private:
    Calculations cal;
}

MainWindow.cpp:

MainWindow::MainWIndow() : ui(new Ui::MainWindow), cal()
{
    //all the initializations...

    QThread *thread_1 = new Qthread;
    cal.moveToThread(thread_1);
    connect(thread_1, SIGNAL(started()), &cal, SLOT(run()));
    thread_1->start();
}

void MainWindow::on_buttonPause_clicked()
{
    // I want to put the Thread pause HERE!
}

I want to have a Pause button on the GUI, so when the user clicks it pauses the qthread (absolutely GUI must not freeze!) and when a user clicks the Resume button it continues the calculations. I've read this topic as well but it didnt help since it uses the QThread subclass as I understood.

Can anybody please help me?

Community
  • 1
  • 1
mohsen_og
  • 774
  • 1
  • 9
  • 31

2 Answers2

3

You can instead of doing a loop make the Qthread do the looping for you using a timer:

Calculations::Calculations(QObject *parent) :QObject(parent)
{
    timerId = startTimer(0);
}
Calculations::pauze(){
    killTimer(timerId);
    timerId = 0;
}
Calculations.restart(){
    timerId = startTimer(0);
}
Calculations::timerEvent(QTimerEvent *event){


    //Heavy calculations per second!



    emit signalResultAvailable;

}

Both pauze and restart are slots connected to the clicked signal of their respective buttons.

If you want it to run only once per second then you can pass 1000 instead of 0 to startTimer.

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
  • @EvgenyS. QThread's default run just starts the event loop. The code in the OP uses a infinite loop which is connected to the `started` signal of the thread. So for the rest of the lifetime of the thread it's "handling" that slot. – ratchet freak Mar 16 '16 at 17:38
  • Yes, I've just missed how they invoke `run()`. Actually, your method is preferrable for them because with custom infinite loop there are a number of pitfalls they should have considered. With standard event loop and QTimer the queued signals/slots will be fine as well as the thread can be stopped with standard API call. – Evgeny S. Mar 16 '16 at 18:03
  • Are you sure that these timer function can be safely called from another thread? Because `QTimer` certainly can't (according to its docs), but for these the docs say nothing. But if you make your `pause()/restart()` methods public slots and call them from the GUI thread using queued connections, I guess it should work fine. – Sergei Tachenov Mar 16 '16 at 18:34
  • @SergeyTachenov At the time it's called in the constructor it's in the main thread. Then the object gets moved to another thread. So Calculator::startTimer only gets called in the thread that the Calculator is in. – ratchet freak Mar 16 '16 at 18:43
  • 1
    `startTimer` and `killTimer` get called from whatever thread calls `pause()` and `restart()`. If that happens to be the GUI thread (and the OP *does* want to pause / restart by GUI command), then they will obviously be called from there, *unless* they are called through a queued/auto connection. So I think you should mention in your answer that these methods shouldn't be called *directly* from the GUI thread. Unless I'm missing something. – Sergei Tachenov Mar 16 '16 at 18:48
  • @SergeyTachenov I did; "Both `pauze` and `restart` are *slots connected to* their respective buttons." With the Implication that AutoConnection would be used which will queue automatically. – ratchet freak Mar 16 '16 at 19:00
  • I see, but IMO it's worth *explicitly* mentioning (and even better, documenting in the comments / API docs) that they should *not* be called from other threads. Otherwise someone, someday may think “hey, let's call this nice slot directly, after all slots may be called directly, right?” Or maybe even better, we could actually check whether the current thread is the right one and if it isn't, schedule a queued callback to the same function using `QTimer::singleShot`. – Sergei Tachenov Mar 16 '16 at 19:05
1

Use paused flag with QMutex and QWaitCondition.

// in header
QMutex mutex;
QWaitCondition waitCondition;

// don't forget to false it in constructor
bool paused;

public slots:
   // connect this slot to GUI button toggled(bool) signal
   void setPaused(enable);

...

// in source
void setPaused(bool enable)
{
   mutex.lock();
   paused=enable;
   if (!paused)
      waitCondition.wakeAll();
   mutex.unlock();
}

void Calculations::run()
{
    while (1)
    {
        mutex.lock();
        if (paused)
        {
           waitCondition.wait(&mutex);
        }
        mutex.unlock();

        unsigned long loopStart = millis();

        //Heavy calculations per second!

        unsigned long loopEnd = millis();
        if ((loopEnd - loopStart) <= 1000)
        {
                delay(1000 - (loopEnd - LoopStart));
                emit signalResultAvailable;
        }
     }
}

UPDATE

Note, that in your implementation you can use signalResultAvailable() only in slots directly connected to the signal with Qt::DirectConnection. If you want to get it back to the main GUI thread with Qt::QueuedConnection you will never get it there. And be aware that all that you connect with Qt::DirectConnection is invoked in the caller thread, so you can not update GUI in that slot.

UPDATE 2

Well, there is something more. Look at this construct:

QThread *thread_1 = new Qthread;
cal.moveToThread(thread_1);
connect(thread_1, SIGNAL(started()), &cal, SLOT(run()));
thread_1->start();

I guess, you want the cal.run() method to be invoked in your thread_1. Sorry, it will be invoked in your current GUI thread. It would be invoked via Qt::AutoConnection in the separate thread if you had declared the void run() as public slot (maybe, it is just hidden behind the //many functions!).

And note again. As I've mentioned, your signal emitted in the custom infinite loop in run() will never go to other thread. Moreover, in your implementation now there is no way to leave your thread: QThread::stop() will not help, because it stops only Qt event loop (which you'll never reach, btw, after you enter your loop) but it does not break the execution in your custom infinite loop.

Evgeny S.
  • 838
  • 4
  • 12
  • I think you can simplify this further -- get rid of the waitCondition, and just have setPaused() lock or unlock the mutex as necessary. That will be enough to keep the thread's while loop blocked whenever the mutex is locked – Jeremy Friesner Mar 16 '16 at 17:07
  • @JeremyFriesner I guess not. QWaitCondition says the thread to sleep, i.e. the thread won't eat CPU resources. In case you just check `paused` and continue the cycle if `paused` is true the thread will be stiil running and consuming CPU resources. – Evgeny S. Mar 16 '16 at 17:13
  • @Evgeny, no, the thread will be blocked on `mutex.lock()`. I've done a similar thing in Java to suspend another thread. Worked perfectly. In fact, this is exactly the case where `QMutexLocker(&mutex);` would make sense! Because it would just lock/unlock the mutex if it isn't locked (not paused), and it would block until it's unlocked otherwise (and then, when you unlock/unpause, it would quickly lock/unlock and continue the loop). – Sergei Tachenov Mar 16 '16 at 18:26
  • @SergeyTachenov Well, I probably misunderstood the meaning thinking that it was suggested just to remove the waitcondition leaveing the rest as is. But still, I wouldn't recommend to widely separate mutex locking/unlocking in code. I think mutex is good to protect a critical section on short range but using it as thread blocking control is not a good practice. One day one will encounter a deadlock on unpaired locking. For example, in this case there is a risk to call setPaused(true) twice. – Evgeny S. Mar 16 '16 at 18:34
  • Yeah, come to think of it, leaving a mutex locked for a long time certainly sounds like a bad idea. Probably not the best thing I did in that Java app, either. – Sergei Tachenov Mar 16 '16 at 18:43