Qt thread: simple, complete and stable (with full sources on GitHub)

Thread is surely one of the most discussed topic on Qt forums. Meanwhile, I never managed to find a simple example which describes how to simply do what I want to do: run a thread to do things and interrupt it if necessary (for example if the GUI is closed). That’s why I’m writing this post (in fact, that’s even why I started this blog…). I hope this will save some time to those who don’t want to spent time on docs and forums, looking for the multiple pieces needed to bring together this solution.

Here is the problem I want to solve in this example:

  • Create a main thread with GUI
  • Start another thread which will do things (counts 60 sec in this example, but it could do whatever you want) and properly closes itself when it is done
  • When the GUI is closed, if the other thread is running, it will force it to abort and then will wait for it to finish as soon as possible

A fully working project with all sources is available for this example on GitHub: https://github.com/fabienpn/simple-qt-thread-example

So, how does it work. First, we need a class which will do the work we want in a separate thread. In this example, this class is called Worker and will do its work in the function doWork(). The doWork() function simply waits 1 sec then increments a counter. This approach is the new “good” approach, compared to the old way of doing it: subclassing QThread. This excellent post explains why subclassing QThread is not relevant anymore since Qt 4.4: You’re doing it wrong…
As we want to display the counter value on the GUI, we add a signal valueChanged() which will be emitted every time the value changed (!).
Now is the interesting part as we need a little more fabric to make this class fully usable in a threading context. First we need a finished() signal. This signal will be emitted when the work is finished to inform the thread that it should stop. Then we need an _abort boolean. When this boolean is set to true the counter loop will stop and the doWork() function will finished. So, basically, all the main thread will have to do to stop the worker is to set _abort to true. We also set a _working boolean to false at the end of the function. This boolean will be checked to define if the Worker is working and thus, if it can be aborted. But things aren’t that simple as accessing a variable from both threads can be dangerous. That’s why a QMutex will be used to protect accesses to _abort and _working (I’m not 100% sure it is needed as read/write accesses might be atomic but it’s always a better practice to protect variables between threads). By the way, this part of the code contains a method to “wait” using a QTimer. This is also a topic which I’ve seen frequently on Qt forums.

void Worker::doWork()
{
    for (int i = 0; i < 60; i ++) {

        mutex.lock();
        bool abort = _abort;
        mutex.unlock();

        if (abort) {
            break;
        }

        // This will stupidly wait 1 sec doing nothing...
        QEventLoop loop;
        QTimer::singleShot(1000, &loop, SLOT(quit()));
        loop.exec();

        emit valueChanged(QString::number(i));
 }

mutex.lock();
_working = false;
mutex.unlock();

 emit finished();
}

The function requestWork() is called to inform that we want the thread to start. It will set _working to true and _abort to false to allow the thread to be interrupted from now on.

void Worker::abort()
{
    mutex.lock();
    _working = true;
    _abort = false;
    mutex.unlock();

    emit workRequested();
}

The function abort() will simply check if the Worker is working and set _abort to true while protecting it. This method of thread interruption is inspired by the Qt Mandelbrot example.

void Worker::abort()
{
    if (_working) {
        _abort = true;
        qDebug()<<"Request worker aborting in Thread "<<thread()->currentThreadId();
    }
}

To ensure that the thread won’t be aborted on its first run, _abort and _working are set to false in class constructor. There is no need to protect variables with mutex here because the Worker is still in the main thread when it is created thus there can’t be concurrent access to variables.

Worker::Worker(QObject *parent) :
    QObject(parent)
{
    _working = false;
    _abort = false;
}

And here is how the Worker class looks like :

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker(QObject *parent = 0);
    void requestWork();
    void abort();

private:
    bool _working;
    bool _abort;
    QMutex mutex;

signals:
    void workRequested();
    void valueChanged(const QString &value);
    void finished();

public slots:
    void doWork();
};

Now the main GUI class. One of the most important rule in Qt threading is that all threads must be stopped before the main thread is exited. That’s why we have to wait for the worker thread to finish in the destructor. But the thread won’t stop unless the counter finished or is interrupted. So we have to manually abort the counter in the destructor then wait for the thread to finish. That’s why we need pointers to both, the thread and the worker in the GUI class.

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QThread *thread;
    Worker *worker;

private slots:
    void on_startButton_clicked();
};

The constructor will instantiate both the Worker and the QThread. Then, the worker is moved to the thread. That will give ownership of this worker to the other thread which will be able to freely manipulate it. Then we connect the Worker::valueChanged() signal to update display. The important part comes after. The workRequested() signal of the Worker is connected to the start() function of the thread which means that the thread will start when requestWork() is called. The started() signal of the thread is connected to the doWork() function of the worker which means that as soon as the thread starts, the doWork() function is called. Then we connect the finished() signal of the worker to the quit() slot of the thread, ensuring that the thread will stop when the work is done. This connection needs to be direct to ensure not being blocked waiting for the thread to exit (with the event enqueued and never processed). This method has been inspired by the excellent post by Maya on this subject: How To Really, Truly Use QThreads; The Full Explanation.

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    thread = new QThread();
    worker = new Worker();

    worker->moveToThread(thread);
    connect(worker, SIGNAL(valueChanged(QString)), ui->label, SLOT(setText(QString)));
    connect(worker, SIGNAL(workRequested()), thread, SLOT(start()));
    connect(thread, SIGNAL(started()), worker, SLOT(doWork()));
    connect(worker, SIGNAL(finished()), thread, SLOT(quit()), Qt::DirectConnection);
}

To start the work in a different thread, we just need to call worker->requestWork() but before calling it, we need to ensure that it is not running. The wait() function is what we need for that. It will wait until the thread finishes and then returns. To ensure we don’t wait the whole 60 secs and finish the work as soon as possible, we call abort() on the worker before:

void MainWindow::on_startButton_clicked()
{
    worker->abort();
    thread->wait(); // If the thread is not running, this will immediately return.

    worker->requestWork();
}

In the destructor, we finish the thread the same way, then delete the objects

MainWindow::~MainWindow()
{
    worker->abort();
    thread->wait();
    delete thread;
    delete worker;

    delete ui;
}

And that’s it. With this method, it is really easy to make “one call” and permanent threads running in a very stable way and without headache. Again, feel free to take a look at the full source code on GitHub and comment on this post if you have question or want to improve this method. I hope this will save some time to some.

Fabien

UPDATE: In that post, it is explained how to have a thread running forever and waiting for requests of various methods. You can head there if you’re interested in this case.

EDIT: Thanks to Sandeep, a problem has been solved. In first version, abort() function didn’t check if work was requested but only if thread was running. It was then possible to request abortion after work request was done but before thread starting, resulting in abortion not being taken into account. That is why the requestWork() has been introduced so work request is done in same thread as abortion request.

Advertisements
Tagged with: , , , , , ,
Posted in Qt
13 comments on “Qt thread: simple, complete and stable (with full sources on GitHub)
  1. accutane says:

    What’s up everyone, it’s my first go to see at this site, and article is actually fruitful for me, keep up
    posting these articles or reviews.

  2. Ralph says:

    Merci Fabien. This was useful.

  3. nelson antunes says:

    Thanks for the sample, very useful.

  4. David Tang says:

    Nice job! Fabien!

  5. Sandeep says:

    That was a fantastic article, and helped me solve some issues in my project. But I found an issue in the below code (which I practically was able to detect during testing).

    void Worker::doWork()
    {
    mutex.lock();
    _abort = false;
    mutex.unlock();
    ……………….
    …………………..
    }

    void Worker::abort()
    {
    mutex.lock();
    _abort = true;
    mutex.unlock();
    }

    The problem with the above code is, if I call thread->start() immediately followed by worker->abort(), then it is not guaranteed that the thread has really started. So your worker->abort() would be called first and then your thread will start next, which will call doWork. In this case, the thread will never be cancelled, because in doWork, we would override _abort = false, which was earlier _abort = true by the abort() function. To get around this, instead of initializing _abort = false in doWork, we can safely initialize _abort = false in Worker’s constructor. In the worker constructor we would not need a mutex, because for 2 reasons:

    1) We are sure, there is no way to access _abort, before the constructor is done.
    2) We have not called moveToThread yet, so worker is not a thread yet, hence no need of a mutex.

    I would be grateful, if someone can comment if my observations are correct.

    • fabienpn says:

      Hi Sandeep !

      Your observation is totally correct. There is a risk that you abort the Worker while the thread is not started, resulting in the Worker doing the work while you didn’t want it to do. Anyway, your solution cannot solve this problem completely. Indeed, if you only set _abort to false in the constructor, first time you will call abort(), _abort will be set to true and it will not be set to false again. So the thread won’t be able to run anymore.

      What you need to do is tell when the Worker can be aborted. It can be aborted as soon as the request for the Worker to work has been done (the problem comes from the gap between this request and the moment the thread actually starts). That’s why I introduce a new method: requestWork() which will set a new variable _working</strong to true and _abort to false and then, request the thread to start. abort() just needs to check if _working</strong is true before setting _abort to true. As both this method run in the main thread, you ensure abort() is taken into account exactly when needed.

      Thank you for pointing out this problem and helped in improving this solution. I’ll update the post and the repository to include theses corrections.

      • Sandeep says:

        I had few more points to add:

        1) Yes, it is possible to detect whether abort() was called first or thread was started first. We can just detect, but cannot control. We can initialize this to _working=true inside “doWork”, so that we are sure, thread has started and we can use abort to reset _working to false.
        2) At the same time, I feel abort() should just cancel a currently running thread, or should set your state to a cancelled state (_abort=true), so that eventually when the thread starts it gets cancelled, because doWork would just return. This way there would not be much complications. Another practical advantage of this is (which I am seeing in my project) is, user can have say for example 2 buttons 1-to start some background work and 2-to navigate to another page. In these scenarios, you can just start thread during button 1 click, then in button 2 click you can just call abort. In this case, irrespective of thread was started or not, because user is navigating to another screen, we just abort the operation.

        I a sure, I am flooding with lot of comments, which might be totally unacceptable to many, please feel free to delete my comments/posts, if you feel so 🙂

      • fabienpn says:

        In fact _working is set to true when you request to start the thread (in the main thread), not when it is actually started. This way, you are sure _abort is set to true whether the thread is started or not.

        The exact routine to start the thread is :
        Worker* worker = new Worker;
        QThread *thread = new QThread;
        worker->moveToThread(thread);
        connect(worker, SIGNAL(workRequested()), thread, SLOT(start()));
        connect(thread, SIGNAL(started()), worker, SLOT(doWork()));
        worker->requestWork(); //Which will set _working to true, _abort to false and emit workRequested().

        This way, if you call abort() immediately after, it will set _abort to true, regardless of whether the thread is actually strated or not. If you don’t call abort() then you are guaranteed _abort is false because it has been set by requestWork().

        The main idea is that you define in the same thread if it is possible to abort or not (by setting _working in requestWork()) and if you want to abort (by setting _abort in abort()). This way, you are sure your _abort is not set before you are concidering the thread working.

  6. Sandeep says:

    Firstly, my requirement was a little different, where in the scope of the thread will be limited to 1 run. What I mean is, once doWork is completed I destroy the thread. My project (requirement) did not had a scenario to re-use the thread. So I didn’t had the issue of thread not picking up the “doWork” second time.

    Secondly, I am not able to understand how exactly introducing another function and state variable, solve the timing issue between actual thread->start() and worker->abort(). If I am not wrong, you might be thinking something like this:

    Sequence 1:

    Worker* worker = new Worker;
    QThread *thread = new QThread;
    worker->requestWork(); //Which will set working to true and abort to false
    worker->moveToThread(thread);
    thread->start();

    But if I call worker->abort() (which will check if _working == true, and which results to be true, you set abort = true), it is still not guaranteed that thread has stated, and if in case, thread is not started, we cannot contain/stop doWork from doing the work. But this will contradict your idea of not doing work, when thread has actually not started.

    Sequence 2:

    Worker* worker = new Worker;
    QThread *thread = new QThread;
    worker->moveToThread(thread);
    thread->start();
    worker->requestWork(); //Which will set working to true and abort to false

    Now, if I can worker->abort() immediately, situation is no different. It is not guaranteed that thread is started before abort().

    I modified you original example according to my needs and the same can be found at:

    http://qt-project.org/forums/viewthread/33156/

    The above example, handles all cases (I suppose), 1) thread started and worker completed work 2) cancel a running thread/worker 3)cancel/clean-up a yet to be started thread.

    You can verify point 3, but commenting the thread->start() in my code and just call worker->abort(), it must not have any side effects.

    The way I look at it, once we say thread->start(), I actually mean that worker should start, otherwise, I do not call thread->start. I would defer calling thread->start() till I am convinced to start. So, even after calling thread->start(), if thread has not started actually, and I call abort, it should be as transparent to me as whether thread had started or not. Ultimately when the thread actually starts late, it just comes out of doWork, as we asked for abort. What I mean is, abort should just abort my activity, irrespective of thread started or not.

    Sorry if I had confused and messed-up.

  7. Vinicius says:

    Very nice! As you said, you gave an perfect example, with all that we would be looking at various links… and you even linked them here!
    Congratulations, and thank you very much for you help!!

  8. Does this implementation works with qt 4.7? And if not what is the problem? I’m new to qt and i have to use 4.7 for an embedded system. The github page says that it only works on >qt4.8 and I would like to know what is the difference… Great work, by the way!

    • fabienpn says:

      Hi Julio ! The reason I said it works on Qt4.8 is because I tested it on this version as it was the last 4.x at the time I wrote the article. I guess it should work on Qt4.7 too but never tested it.

  9. tmoreau says:

    Hi,
    Those explanations were really useful, thanks !

    I translated this example in python using PyQt and put it here:
    https://github.com/tymoreau/simple-qt-thread

    I also spotted one typo in the second block of code:
    “void Worker::abort()” should be “void Worker::requestWork()”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Author

My name is Fabien Pierre-Nicolas. I’m a software developer currently working on various project from embedded systems to web development. Programming is my job and my hobby and I started this blog to share some of my humble experiences. Oh, and I’m French, and as any good French, my English is terrible. So please forgive me for that.

Links
Categories