GoodDeeds
GoodDeeds

Reputation: 8527

Why is a slot being called in receiver's thread even after using Qt::DirectConnection? How do I ensure that it is called in the other thread?

According to the documentation for Qt::ConnectionType in Qt5, using Qt::DirectConnection means that the slot for a given signal is called in the same thread as the signal itself, even if the object the slot belongs to lies in a different thread.

In my application, I am creating a server, and when a new connection is received, I create a new thread, a new QWebSocket object in it, and connect certain QWebSocket signals to slots defined in the class of the server (the same class where the connection is received and the thread is created).

However, though the thread is created successfully, the slot is being called in the main thread.


Here is a simpler example that simulates what I am doing, an MCVE:

Base.h file:

#ifndef BASE_H
#define BASE_H

#include<QThread>
#include<thread>
#include<QObject>
#include "emitcaller.h"
#include <QDebug>

class Base : public QObject
{
    Q_OBJECT
public:
    EmitCaller *emitCaller;
    void create_thread();
    void make_emit();

public slots:
    void do_something();

};

#endif // BASE_H

This represents the server class. create_thread() is like the function when a new connection from a client is to be done. do_something() is the slot that needs to be executed when the QWebSocket receives a signal.

Base.cpp file:

#include "base.h"
#include "emitcaller.h"
#include<QEventLoop>
#include <mutex>
#include <condition_variable>

void Base::create_thread()
{
    std::mutex mutex;
    std::condition_variable cv;
    std::thread t = std::thread([&](){
        EmitCaller *ec = new EmitCaller;
        this->emitCaller = ec;
        qDebug() << "thread created, now in thread " << QThread::currentThread();
        QObject::connect(ec,SIGNAL(my_signal()),this,SLOT(do_something()),Qt::DirectConnection);
        cv.notify_all();
        QEventLoop loop;
        loop.exec();
    });
    std::unique_lock<std::mutex> lock(mutex);
    cv.wait(lock); //wait till connect() completes, so that signal sent is received after that
    t.detach();
}

void Base::do_something()
{
    qDebug() << "doing something in thread " << QThread::currentThread();
}

void Base::make_emit()
{
    qDebug() << "called make_emit in thread " << QThread::currentThread();
    emitCaller->do_emit();
}

Next, EmitCaller.h file:

#ifndef EMITCALLER_H
#define EMITCALLER_H

#include <QObject>
#include <QDebug>

class EmitCaller : public QObject
{
    Q_OBJECT
public:
    void do_emit();
signals:
    void my_signal();
};

#endif // EMITCALLER_H

This is to simulate the QWebSocket. The my_signal() signal is the one that the QWebSocket in the program receives to call the do_something() slot. make_emit() is an extra function, just to ask the signal to be emitted, created only for the sake of this simplified example.

EmitCaller.cpp file:

#include "emitcaller.h"

void EmitCaller::do_emit()
{
    emit my_signal();
}

main.cpp file:

#include <QApplication>
#include "base.h"
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Base b;
    qDebug() << "main in thread " << QThread::currentThread();
    b.create_thread();
    b.make_emit();

    return a.exec();
}

The output is as follows:

main in thread  QThread(0xc20180)
thread created, now in thread  QThread(0x7fb9680009e0)
called make_emit in thread  QThread(0xc20180)
doing something in thread  QThread(0xc20180)

Now, as per my understanding, the following happens:

  1. create_thread() is called. The EmitCaller object (QWebSocket object) is created inside a new thread. Thus, the thread affinity of the object should be the new thread, and all signals sent from it should be from the new thread.
  2. connect() is done using Qt::DirectConnection. So, the slot do_something() should be called in this new thread, even though its class object, b, lies in the main thread.
  3. do_emit() is called on the object whose thread affinity is with the new thread, which should result in the behaviour expected as described above.

I expect the output to instead be:

main in thread  QThread(0xc20180)
thread created, now in thread  QThread(0x7fb9680009e0)
called make_emit in thread  QThread(0xc20180)
doing something in thread  QThread(0x7fb9680009e0)

A few additional points:

  1. In my server program, I don't have a pointer pointing to the QWebSocket object. However, the signal there is generated when a new client connects. To send the signal myself, I have created a pointer to access the object.
  2. I need to use std::thread, I cannot use QThread for this.

Thank you.

Upvotes: 0

Views: 1167

Answers (1)

king_nak
king_nak

Reputation: 11513

You have to consider 3 threads in your problem statement:

  1. The thread the receiver lives in
  2. The thread the sender lives in
  3. The thread that is emitting the signal

Queued/BlockingQueued connections ensure that the slot will be executed in the receivers thread.

DirectConnection executes the slot in the current thread. This is not always the thread the sender lives in! In fact, there is no standard way to enforce running a slot in the sender's thread (because usually, the receiver's thread is what you want).

NB: AutoConnection uses QueuedConnection if the current thread and receiver thread are not the same, otherwise DirectConnection

To solve your problem, you could forcably switch to the sender's thread if you're on another thread, like this:

In EmitCaller, add

private: Q_INVOKABLE my_thread_do_emit() { do_emit(); }

Then in the implemetation:

void EmitCaller::do_emit()
{
    if (this->thread() != QThread::currentThread()) {
        QMetaObject::invokeMethod(this, "my_thread_do_emit", Qt::BlockingQueuedConnection);
    } else {
        emit my_signal();
    }
}

However, I would suggest that you reconsider your design. It seems unusual to call a slot in a certain foreign thread. Maybe there is a problem in your thread affinity setup... (e.g. the receiver should live in the newly created thread)

Upvotes: 4

Related Questions