0

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.

  • Why is the slot being called in the receiver's thread, instead of the emitter's thread, even though Qt::DirectConnection is used?

  • If this is incorrect, where am I going wrong? (I am new to the signal-slot system of Qt).

  • If this cannot be done this way, how can I achieve the behaviour I want? I would like do_something() to run in a separate thread.

Thank you.

GoodDeeds
  • 7,956
  • 5
  • 34
  • 61
  • According to your output the signal and the slot *are* being executed in the same thread. – Emerald Weapon Mar 06 '17 at 15:37
  • @EmeraldWeapon But the slot is executed in the main thread. Is the signal also in the main thread? If so, why, as the EmitCaller object is being created in the new thread? – GoodDeeds Mar 06 '17 at 15:55
  • The thread on which the EmitCaller object was created is irrelevant. You're using a direct connection which means the slot will be invoked on the same thread as that from which the signal was emitted. – G.M. Mar 06 '17 at 16:01
  • @G.M. Oh, so that means it does not matter _which object emitted the signal_? If that is the case, in my case, suppose I have, say, a`QWebSocket::disconnected` signal. I would have expected that the "thread of the signal" to be the same as that of the `QWebSocket` (or `EmitCaller` here). If that is not so, how do I ensure that corresponding slot is executed in the new thread I have created? – GoodDeeds Mar 06 '17 at 16:06
  • Well, your code seems far more convoluted that it should be but... I think what you're looking for is a [`Qt::QueuedConnection`](http://doc.qt.io/qt-5/qt.html#ConnectionType-enum). Regarding your threading model you should probably look at the example code given for [`QThread`](http://doc.qt.io/qt-5/qthread.html). – G.M. Mar 06 '17 at 16:12
  • @G.M. According to the documentation, `Qt::QueuedConnection` runs the slot in the receiver's thread. In my case, `b` is the receiver, which is in the main thread. As per my question, how can I get it to run in the new `std::thread` created instead? – GoodDeeds Mar 06 '17 at 16:17
  • On a side note, you're using `condition_variable` incorrectly. `wait` can wake up spuriously. You must always have an actual condition being checked after the wake-up. – Sebastian Redl Mar 06 '17 at 16:17
  • For the main topic, are you sure the `QWebSocket`'s thread affinity is your new thread in the first place? Pretty sure that requires special internal thread setup that is only done for threads created with `QThread`. https://forum.qt.io/topic/59687/qthread-or-std-thread/5 – Sebastian Redl Mar 06 '17 at 16:27
  • The important bit is that the sending object's `thread()` is immaterial. The only threads that matter is the one where you invoke `emit mySignal` and the thread of the object connected to the signal. – Kuba hasn't forgotten Monica Mar 06 '17 at 16:35
  • @KubaOber Ok, then if that is the case, how can I ensure that the slot is called in the thread in `create_thread`? As I mentioned before, I am trying to implement a server where each `QWebSocket` is in a different thread so that the slots called on each has a dedicated thread to execute in. How can I do this? – GoodDeeds Mar 06 '17 at 16:41

1 Answers1

4

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)

king_nak
  • 11,313
  • 33
  • 58
  • 1
    Another solution: `postToThread([&]{ sender->method(); }, sender);` using [this answer](http://stackoverflow.com/a/21653558/1329652). The emitting thread is then immaterial. – Kuba hasn't forgotten Monica Mar 06 '17 at 16:33
  • Thank you! This works. (+1) As I mentioned in my question, my actual application is a server in which I want one thread per `QWebSocket` object. I want all the subsequent slot execution corresponding to thesignals received by the `QWebSocket` object to run in their dedicated threads. So, if the design is not good, (as per point 3 of my question) could you please suggest how I could improve on it? – GoodDeeds Mar 06 '17 at 16:36
  • Also, I did not fully understand what this code does. How can `this->thread()` be different from `QThread::currentThread`? What is the code doing if so? Thank you. – GoodDeeds Mar 06 '17 at 16:38
  • 1
    @GoodDeeds `Foo::thread()` is a requirement for non-thread-safe methods, not a statement of fact. By calling any method of `Foo` from a different thread, you're asserting that such a method is thread-safe. It's on you to call the thread-unsafe methods from the right thread. C++ provides no mechanism to do this automagically for you. But see [this question](http://stackoverflow.com/q/40382820/1329652) for ideas about making methods thread-safe, including universal wrappers. – Kuba hasn't forgotten Monica Mar 06 '17 at 16:53
  • have a look at this article: http://doc.qt.io/qt-5/threads-qobject.html#qobject-reentrancy Especially the section _Event driven objects may only be used in a single thread.[...] you cannot start a timer or connect a socket in a thread that is not the object's thread._ Also, the handling of received data (through the QWebSocket) should be done in the socket's thread, and therefore in the class holding/subclassing the socket. – king_nak Mar 08 '17 at 08:13