C++ 为什么在接收器';即使在使用Qt::DirectConnection之后,是否仍会恢复线程?如何确保在另一个线程中调用它?

C++ 为什么在接收器';即使在使用Qt::DirectConnection之后,是否仍会恢复线程?如何确保在另一个线程中调用它?,c++,multithreading,qt,signals-slots,stdthread,C++,Multithreading,Qt,Signals Slots,Stdthread,根据Qt5中的说明,使用Qt::DirectConnection意味着给定信号的插槽与信号本身在同一线程中调用,即使插槽所属的对象位于不同的线程中 在我的应用程序中,我正在创建一个服务器,当接收到一个新连接时,我在其中创建一个新线程,一个新的QWebSocket对象,并将某些QWebSocket信号连接到服务器类中定义的插槽(接收连接并创建线程的同一类) 但是,尽管线程已成功创建,但正在主线程中调用插槽 下面是一个模拟我正在做的事情的简单示例,即MCVE: Base.h文件: #ifndef

根据Qt5中的说明,使用
Qt::DirectConnection
意味着给定信号的插槽与信号本身在同一线程中调用,即使插槽所属的对象位于不同的线程中

在我的应用程序中,我正在创建一个服务器,当接收到一个新连接时,我在其中创建一个新线程,一个新的
QWebSocket
对象,并将某些
QWebSocket
信号连接到服务器类中定义的插槽(接收连接并创建线程的同一类)

但是,尽管线程已成功创建,但正在主线程中调用插槽


下面是一个模拟我正在做的事情的简单示例,即MCVE:

Base.h文件:

#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
接下来,EmitCaller.h文件:

#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
main.cpp文件:

#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();
}
#include "emitcaller.h"

void EmitCaller::do_emit()
{
    emit my_signal();
}
#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();
}

现在,根据我的理解,发生了以下情况:

  • 调用
    create\u thread()
    EmitCaller
    对象(
    QWebSocket
    对象)是在新线程中创建的。因此,对象的线程关联性应该是新线程,从它发送的所有信号都应该来自新线程
  • connect()
    使用
    Qt::DirectConnection
    完成。因此,在这个新线程中应该调用插槽
    do_something()
    ,即使它的类对象
    b
    位于主线程中
  • do_emit()
    在线程关联与新线程的对象上被调用,这将导致如上所述的预期行为
  • 我希望输出结果是:

    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)
    

    还有几点:

  • 在我的服务器程序中,我没有指向
    QWebSocket
    对象的指针。但是,那里的信号是在新客户端连接时生成的。为了自己发送信号,我创建了一个指针来访问对象
  • 我需要使用
    std::thread
    ,我不能为此使用
    QThread

    • 为什么在接收方线程中调用插槽而不是 发射器的线程,即使使用了
      Qt::DirectConnection

    • 如果这是不正确的,我会错在哪里?(我不熟悉Qt的信号槽系统)

    • 如果不能这样做,我如何才能实现我想要的行为?我想
      do\u something()
      在单独的线程中运行


    谢谢。

    < P>你在问题陈述中必须考虑3个线程:

  • 接收器所在的线程
  • 发送者所在的线程
  • 发出信号的线程
  • 排队/阻塞排队连接确保插槽将在接收方线程中执行

    DirectConnection执行当前线程中的插槽。这并不总是发件人所在的线程!事实上,没有标准的方法来强制在发送方的线程中运行一个插槽(因为通常,接收方的线程就是您想要的)

    注意:如果当前线程和接收器线程不相同,则自动连接使用QueuedConnection,否则使用DirectConnection

    要解决您的问题,如果您在另一个线程上,您可以不可禁用地切换到发件人的线程,如下所示:

    在EmitCaller中,添加

    private: Q_INVOKABLE my_thread_do_emit() { do_emit(); }
    
    然后在实施中:

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

    然而,我建议你重新考虑你的设计。在某个外部线程中调用插槽似乎不常见。也许你的线程关联设置有问题。。。(例如,接收者应该生活在新创建的线程中)

    在问题陈述中必须考虑3个线程:

  • 接收器所在的线程
  • 发送者所在的线程
  • 发出信号的线程
  • 排队/阻塞排队连接确保插槽将在接收方线程中执行

    DirectConnection执行当前线程中的插槽。这并不总是发件人所在的线程!事实上,没有标准的方法来强制在发送方的线程中运行一个插槽(因为通常,接收方的线程就是您想要的)

    注意:如果当前线程和接收器线程不相同,则自动连接使用QueuedConnection,否则使用DirectConnection

    要解决您的问题,如果您在另一个线程上,您可以不可禁用地切换到发件人的线程,如下所示:

    在EmitCaller中,添加

    private: Q_INVOKABLE my_thread_do_emit() { do_emit(); }
    
    然后在实施中:

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

    然而,我建议你重新考虑你的设计。在某个外部线程中调用插槽似乎不常见。也许你的线程关联设置有问题。。。(例如,接收器应位于新创建的线程中)

    根据您的输出,信号和插槽在同一线程中执行。@emeraldwill,但插槽在主线程中执行。信号是否也在主线程中?如果是,为什么,因为EmitCaller对象是在新线程中创建的?创建EmitCaller对象的线程是不相关的。您使用的是直接连接,这意味着插槽将在发出信号的线程所在的同一线程上调用。@G.M.噢,这意味着发出信号的对象并不重要?如果是这样的话,在我的例子中,假设我有一个
    QWebSocket::disconnected
    信号。我希望“信号线程”与
    QWebSocket
    (或
    EmitCaller
    )的线程相同。如果不是这样,我如何确保在我创建的新线程中执行相应的slot呢?好吧,您的代码看起来要复杂得多,但。。。我想你要找的是一份工作。关于您的线程模型,您可能应该查看为提供的示例代码。根据您的输出,信号和插槽正在同一个线程中执行。@emeraldwealth,但插槽是ex