4
votes

While trying to make a multi-camera system work with a different thread handling a different camera, I couldn't get signals and slots working correctly between different threads. I knew something was wrong with the fact that the object sending the signal and the related slot's object were living in different threads, and thus I knew that I probably only had to find an appropriate "connection type" parameter for the connection. Eventually, I ended up discovering that only using Qt::DirectConnection would make everything work as it should.

Find the simplified code below. Here's a small description of how everything should work.

  • Application is the main program which is supposed to create all the threads and start them. In this simplified version, it then simply waits for the worker to finish its jobs through the slot "quit".

  • Worker is the object which performs one of the threaded tasks. In this simplified example, I just wait some time before finishing the computation. The worker then emits a signal which is directed to the application instance, which is then allowed to wait for all the threads and quit QCoreApplication.

What I am finding is that if I don't use Qt::DirectConnection in the second connect, then the finished() signal of the worker does not trigger the quit() slot of the thread, which means that the application then remains hanging waiting for the thread.

My question is: why is that so? since the two objects (the worker and the thread) belong to different threads, shouldn't I be using QueuedConnection, or something else? I thought DirectConnection should only be used for objects belonging to the same thread.

main.cpp:

#include <iostream>
#include <QCoreApplication>
#include "program.h"

using namespace std;

int main(int argc, char **argv) {
  QCoreApplication app(argc, argv);

  Program *p = new Program;
  p->execute();

  app.exec();

  delete p;
}

program.h

#ifndef _PROGRAM_H_
#define _PROGRAM_H_

#include <QThread>
#include <QTimer>
#include "worker.h"

class Program: public QObject {
  Q_OBJECT

  private:
    Worker *worker;
    QThread *thread;

  public:
    void execute();

  public slots:
    void quit();
};

#endif // _PROGRAM_H_

program.cpp

#include "worker.h"

using namespace std;

void Program::execute() {
  worker = new Worker();
  thread = new QThread;
  worker->moveToThread(thread);

  cout << "Connection established: "
        << connect(thread, SIGNAL(started()), worker, SLOT(process()))
        << endl;

  // slot not called if I remove the fifth parameter
  // or if I put Qt::QueuedConnection
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(quit()),
                    Qt::DirectConnection)
        << endl;

  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), this, SLOT(quit()))
        << endl;

  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()))
        << endl;

  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()))
        << endl;

  thread->start();
}

void Program::quit() {
  cout << "waiting.." << endl;
  thread->wait();
  cout << "           .. I'm done!" << endl;

  cout << "quitting from all.." << endl;
  QCoreApplication::quit();
  cout << "           .. I'm done!" << endl;
}

#include "program_moc.cpp"

worker.h

#ifndef _WORKER_H_
#define _WORKER_H_

#include <QObject>

class Worker: public QObject {
  Q_OBJECT

  public slots:
    void process();

  signals:
    void finished();
};

#endif // _WORKER_H_

worker.cpp:

#include <iostream>
#include <unistd.h>
#include "worker.h"

using namespace std;

void Worker::process() {
  cout << "Worker::process() started" << endl;
  usleep(1000000);

  cout << "Worker::finished() being emitted" << endl;
  emit finished();

  cout << "Worker::process() finished" << endl;
}

#include "worker_moc.cpp"

EDIT

Following @ariwez 's answer does solve the problem in this specific simplified example, but it doesn't in a slightly more complex one, which I am adding now.

In this example,

  • Program has its own job to execute periodically through the use of a QTimer. Program also has yet another QTimer which is used to simulate the user quitting the program, which triggers the execution of the slot Program::quit().

  • Worker executes its own job until his quitting flag gets set to false. This is done inside Program::quit().

As in the previous example, worker successfully finishes its procedure and emits the finished() signal, which is also supposed to be connected with the thread's quit() slot. However, somehow the slot must not be executed, because Program hangs waiting for the thread. Differently from the previous example, relocating the moveToThread procedure does not solve the issue: everything works if and only if I use the Qt::DirectConnection type for the connection between Worker::finished() and QThread::quit(), and I can't understand why.

main.cpp: same as above

program.h:

#ifndef _PROGRAM_H_
#define _PROGRAM_H_

#include <QThread>
#include <QTimer>
#include "worker.h"

class Program: public QObject {
  Q_OBJECT

  private:
    QTimer *timer, *quittingTimer;
    Worker *worker;
    QThread *thread;

  public:
    ~Program();

    void execute();

  private slots:
    void quit();
    void update();
};

#endif // _PROGRAM_H_

program.cpp:

#include <iostream>
#include <QCoreApplication>
#include "program.h"
#include "worker.h"

using namespace std;

Program::~Program() {
  delete timer;
  delete quittingTimer;
}

void Program::execute() {
  timer = new QTimer();
  timer->setInterval(500);
  connect(timer, SIGNAL(timeout()), this, SLOT(update()));

  worker = new Worker;
  thread = new QThread;

  cout << "Connection established: "
        << connect(thread, SIGNAL(started()), worker, SLOT(process()))
        << endl;

  // doesn't work if I remove Qt::DirectConnection
  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(quit()),
                    Qt::DirectConnection)
        << endl;

  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), this, SLOT(quit()))
        << endl;

  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()))
        << endl;

  cout << "Connection established: "
        << connect(worker, SIGNAL(finished()), thread, SLOT(deleteLater()))
        << endl;

  worker->moveToThread(thread);

  timer->start();
  thread->start();

  // simulates user pressing key to close program
  quittingTimer = new QTimer();
  quittingTimer->singleShot(4000, this, SLOT(quit()));
}

void Program::quit() {
  cout << "timer->stop()" << endl;
  timer->stop();

  cout << "worker->quit()" << endl;
  worker->quit();

  cout << "thread->wait()" << endl;
  thread->wait();

  cout << "qcore->quit()" << endl;
  QCoreApplication::quit();
}

void Program::update() {
  cout << "Program::update() called" << endl;
}

#include "program_moc.cpp"

worker.h:

#ifndef _WORKER_H_
#define _WORKER_H_

#include <QObject>

class Worker: public QObject {
  Q_OBJECT
  private:
    bool quit_flag;

  public:
    void quit();

  public slots:
    void process();

  signals:
    void finished();
};

#endif // _WORKER_H_

worker.cpp:

#include <iostream>
#include <unistd.h>
#include <QThread>
#include "worker.h"

using namespace std;

void Worker::quit() {
  quit_flag = true;
}

void Worker::process() {
  quit_flag = false;
  while(!quit_flag) {
    cout << "Worker::process() is processing" << endl;
    usleep(300000);
  }
  cout << "Worker::finished() is being sent" << endl;
  emit finished();
  cout << "Worker::finished() is sent" << endl;
}

#include "worker_moc.cpp"

EDIT 2

Re-reading the article in @ariwez 's link I found out which was the problem in this second example. The problem was that the main event loop was being interrupted, while waiting for the thread's QThread::finished() signal, and so the Worker::finished() signal could not be dispatched into the QThread::quit() slot. So yeah, basically I deadlocked myself.

2
@ariwez: Thank you, I think I found out what the problem was. I had already read that article but I decided to re-read it again just to make sure I understood everything correctly.. I'll update my question with the correct analysis. If you write another answer (what happened your previous one?) I'll select it.user2152106
Glad to help you. I had to rethink my answer, so that I hid it (in hope that better solution will be given from me or someone else) ;)ariwez
@user2152106 I have the same question recently. I don't see a explicit answer here. Did you mean we don't have to QThread.wait(), and just set worker's quit_flag = true? to prevent the deadlock?minion

2 Answers

2
votes

You moveToThread before connects that's why everything is in one thread.

1
votes

I think I see what the issue might be.

Your directConnection, which is really bad thing to do accross theads as it acts like an interrupt - executs immediatley. But it is BECAUSE it executes immediatley that it works in your case. When the thread closes down it emits the quit() signal.... then usually it "quits" but in direcetConnection it immediatley executes the slot finished() and then quits.

If you have a QueuedConnection then it would emit the signal to the queue and then finish "quitting". However once the thread is stopped the thread queue is never servied so your finished() function is never run.

Perhaps a better way to do this would be to connect to your object finished() send your signal there and then get the object within the thread to stop the thread itsself. (i.e. don't stop the thread remotly, stop the object, which in turn stops its thread).