2

So I have a simple Qt4 application with a Start button, a Stop button, and a text field. When the Start button is pressed, a new thread is spawned that continually increments a counter (in a while loop), and the text field is updated to reflect this via signals/slots. When the Stop button is pressed, the counting stops, until the Start button is pressed again.

It works ... sort of. It only updates the counter once per second; I want it to go faster, say 44100 times per second. Taking out the sleep(1) call, however, causes the while loop to block, no GUI events are recognized, and the application freezes. Also, the Stop button only works on the second try?

This used to work pretty much fine when I was subclassing QThread, but I was told not to do that, so I tried making a solution with subclassing QObject and then moving the object to a QThread ... but it isn't working as well as it used to.

Here's the code:

Worker.h

class Worker : public QObject
{
  Q_OBJECT

  public:
    Worker();

  public slots:
    void process();
    void stopRunning();

  signals:
    void signalValueUpdated(QString);

  private:
    bool running;
};

Worker.cpp

#include "Worker.h"

void Worker::process()
{
    qDebug("Worker thread id %d",(int)QThread::currentThreadId());

    static int value=0;
    running = 1;
    while(running == 1)
    {
        QString string = QString("value: %1").arg(value++);
        sleep(1); //I want to take this out or make it way shorter but I can't without it crashing.

        emit signalValueUpdated(string);

        QCoreApplication::processEvents();       
    }
}

void Worker::stopRunning()
{
    qDebug("stopRunning() invoked");
    running = 0;
}

MainWindow.h

class MainWindow : public QWidget
{
  Q_OBJECT

  public:
    MainWindow(QWidget *parent = 0);

  private:
    //Widgets
    QHBoxLayout * boxLayout;
    QPushButton * startButton;
    QPushButton * stopButton;
    QLineEdit * lineEdit;

    //Thread where the worker lives
    QThread * workerThread;
    //Worker object itself
    Worker * worker;
};

MainWindow.cpp

#include "MainWindow.h"

 MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
{
    boxLayout = new QHBoxLayout(this);
    startButton = new QPushButton("Start Counter", this);
    stopButton = new QPushButton("Stop Counter", this);
    lineEdit = new QLineEdit(this);

    boxLayout->addWidget(startButton);
    boxLayout->addWidget(stopButton); 
    boxLayout->addWidget(lineEdit);

    qDebug("Thread id %d",(int)QThread::currentThreadId());

    workerThread = new QThread;
    worker = new Worker();
    worker->moveToThread(workerThread);

    connect( startButton, SIGNAL(clicked()), workerThread, SLOT(start()), Qt::QueuedConnection ); //When the start button is clicked, start the worker thread
    connect( startButton, SIGNAL(clicked()), worker, SLOT(process()), Qt::QueuedConnection ); //When the start button is clicked, start the worker thread
    connect( workerThread, SIGNAL(started()), worker, SLOT(process()), Qt::QueuedConnection ); //When the worker thread starts, begin the Worker object's process function
    connect( stopButton, SIGNAL(clicked()), worker, SLOT(stopRunning()), Qt::QueuedConnection ); //When the stop button is clicked, invoke the Worker's stopRunning()
    connect( worker, SIGNAL(signalValueUpdated(const QString&)), lineEdit, SLOT(setText(const QString&)), Qt::QueuedConnection ); //When the Worker emits a signalValueChanged, update the lineEdit to reflect this

}

Without sleep(1) and processEvents() in Worker.cpp, the whole thing crashes, but with them it is slowed down too much, only updating the number once per second instead of 1000 or more. How can I make the while(running) loop not block?

Thanks in advance! Still trying to wrap my head around the best way to do multithreading in Qt.

evdc
  • 185
  • 1
  • 3
  • 8
  • Do you know `QRunnable`? It represents one work item which can then be run through `QThreadPool::globalInstance()->start(runnable)` without bothering about the target thread in which it will run. Just an idea for an alternative solution. Starting individual `QThread` instances is more low-level, `QThreadPool` and `QtConcurrent` are more high-level. – leemes Mar 05 '13 at 08:22
  • Also, may I ask why are you using `Qt::QueuedConnection`? – Lorenzo Dematté Mar 05 '13 at 08:25
  • You're asking too much for the process to do - updating the GUI on a tight loop like that. It's probably crashing because the worker event queue is getting too big for a single process (eg 2Gb) - watch the process working memory. There's very little point in trying to update the GUI 44100 times a second - nobody can read it! – Roger Attrill Mar 05 '13 at 09:49

1 Answers1

2
  1. Remove QCoreApplication::processEvents() in the worker, if you don't need it (why do you need it? GUI events should be already processed by the main thread...). That may be the cause of your problem.

  2. Connect thread signals in the right way:

    connect(workerThread, SIGNAL(started()), worker, SLOT(process()));
    connect(startButton, SIGNAL(clicked()), thread, SLOT(start()));
    

    (remove the connect of startButton.clicked()->worker.process() - it is useless and does not do what it is written in the comment)

    Why should you remove it?

    Because connecting the button directly to the process in the worker is not right. You should connect the button to the start of the Thread, then the Thread (with started) to the worker process(). Avoiding direct connections should avoid the GUI freeze problems you are facing.

Also, you need to create a new thread and "fire" it every time you click on the button. You may do it by wiring the button to a SLOT in the window, and create the thread from it (creating it in the MainWindow constructor will not work).


Taken from a (working) program I have:

  // You need another slot in MainWindow, let it be "startProcessing()"
  // in MainWindow::MainWindow, connect the start button to startProcessing()
  connect(btnStart, signal(clicked(), this, SLOT(startProcessing())


  // inside the startProcessing slot
  void MainWindow::startProcessing() {
     ...
     Worker* worker = new Worker;
     QThread* thread = new QThread;

     // start the work
     worker->moveToThread(thread);
     connect(thread, SIGNAL(started()), worker, SLOT(process()));

     // Take care of cleaning up when finished too
     connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
     connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
     connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

     thread->start(); 
  }

As you may have noticed, I added code to clean it up as well (deleteLater()).

The stop button will also give you some problems, but you should have now a good idea on how to proceed.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Lorenzo Dematté
  • 7,638
  • 3
  • 37
  • 77
  • I did that, and now once process() has started (once the while() loop is running), no GUI input is accepted -- clicking the Stop button does nothing, the debug message "stopRunning() invoked" never shows up. Putting processEvents() back in solved that... Edit again: And removing that one connect makes it so that once Start has been pressed and then Stop has been pressed, then pressing Start again does nothing. – evdc Mar 05 '13 at 08:46
  • 1
    ..then you're doing it wrong - your code is running in the GUI thread! – Martin James Mar 05 '13 at 08:48
  • @MartinJames is right, if user input is not accepted, you are running the worker in the main (GUI) thread. The connect from the button to the worked should be the problem! – Lorenzo Dematté Mar 05 '13 at 08:54
  • Well that would definitely explain it -- how do I fix that? I thought by calling worker->moveToThread(workerThread) that would be avoided, the worker object would be moved to a seperate thread and its code would run there. Edit: Also, the thread id that debug printout displays is different... – evdc Mar 05 '13 at 08:54
  • @user2134278 I think the problem is in the connect... I'll edit the answer and take a snippet from some working code. – Lorenzo Dematté Mar 05 '13 at 08:56
  • @user2134278 ok, you have another issue here: `removing that one connect makes it so that once Start has been pressed and then Stop has been pressed, then pressing Start again does nothing`. This is because you need to spawn a new thread each time. You have to move thread creation in response of "clicked". I'll edit again :) – Lorenzo Dematté Mar 05 '13 at 09:03
  • Thanks! that is definitely helpful. :) The stop button is, like you say, problematic -- since I was also connecting that to a slot in the worker. If I don't do that, though, I'm not sure how to get the information that "the stop button has been pressed" over to the Worker object living in its other thread, so that it can call stopRunning() and set running to 0. Would events work for this? – evdc Mar 05 '13 at 09:18
  • They should work, but why do not directly set the `running` variable from the btnFinished click? (NOTE: In the general case, I would NOT advise that, as it may have running conditions. However, in this case it should be OK, if you just want the thread to stop in a few cycles after pressing the button..) (Or, if you want to be safe, mark the `running` variable as volatile). – Lorenzo Dematté Mar 05 '13 at 10:06
  • So, to clarify: you're basically saying that signals and slots don't work between objects that are in different threads. I can't send a signal from a QPushButton in one thread to a Worker in another. I have to use some intermediary (like a QThread, or ...something else?) I guess I just thought that signals and slots worked across threads in Qt, and was misled by that. – evdc Mar 05 '13 at 10:15
  • @user2134278 No, I'm not saying that. I'm saying that you have to be careful and understand in which thread your objects are really living. Your problems were exactly due to the fact that your objects were all living in the main (GUI) thread. See this answer http://stackoverflow.com/a/638693/863564 for a start, but than be sure to read also http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/ on how to do it properly (using moveToThread) – Lorenzo Dematté Mar 05 '13 at 10:23