(QNativeSocketEngine)QObject:无法为不同线程中的父级创建子级

Posted

技术标签:

【中文标题】(QNativeSocketEngine)QObject:无法为不同线程中的父级创建子级【英文标题】:( QNativeSocketEngine) QObject: Cannot create children for a parent that is in a different thread 【发布时间】:2016-03-16 13:22:26 【问题描述】:

我遇到了一个问题,曾多次在此处询问类似情况,但我在这些主题中找不到解决方案。

我有一个主类,我想通过 q​​t-network 支持来扩展它,并通过一个附加类来扩展它。让我将源代码分解为相关部分:

主类

.h

class MainClass: public QObject
    Q_OBJECT
public:
[...]
private:
    NetworkSupport * netSupport;
;

.cpp

MainClass::MainClass()

    [...]
    netSupport
    netSupport = new NetworkSupport(this->thread());
    netSupport->start();
    [...]

网络类

.h

class NetworkSupport : public QThread

    Q_OBJECT
public:
    NetworkSupport(QThread *mainThread);
    ~NetworkSupport();

    QByteArray readData();
    void sendData(QByteArray *msg);

public slots:
    void acceptConnection();
    void receive();
    void run();

private:
    QByteArray curMessage;
    QThread* libThread;
    QEventLoop initWlan;

protected:
    QTcpServer server;
    QTcpSocket* client;
;

.cpp

NetworkSupport::NetworkSupport(QThread* mainThread)

    libThread = mainThread;
    server.moveToThreaD(libThread);
    server.listen(QHostAddress::Any, 5200);

    QMetaObject::invokeMethode(&initWlan, "quit", Qt:QueuedConnection);
    initWlan.exec();


void NetworkSupport::run()
    connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));


void NetworkSupport::acceptConnection()

    client = server.nextPendingConnection();
    client->moveToThread(libThread);

    if (client->state() == QAbstractSocket::ConnectedState)
        connect(client, SIGNAL(readyRead()), this, SLOT(receive()));



void NetworkSupport::receive()

    curMessage = client->readAll();


void NetworkSupport::sendData(QByteArray* msg)

    if(client->state() == QAbstractSocket::ConnectedState)
    
        client->moveToThread(libThread);
        printf("Sending a message %s\n",msg->data());
        client->write(*msg);
        client->waitForBytesWritten();
    

我知道我通常不需要这么多地指定moveToThread(),但如果它们都存在或被删除,它最终不会改变任何事情。

但是在执行时我在sendData() 中的client->write(*msg) 处收到错误消息,即:

[...]
Sending a message ?r
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QnativeSocketEngine(0x2afc660), parent's thread is QThread(0x1b59210), current thread is QThread(0x7f6a70026b60)
QSocketNotifier: Can only be used with threads started with QThread
[...]

它看起来确实像一个警告,因为该程序之后仍在执行,但我没有收到来自客户端的任何消息(即receive() 永远不会执行),我猜这是因为最后一行来自错误消息。

会不会是这个错误信息只是具有误导性,如果是这样,它的实际含义是什么,还是我做错了什么?

【问题讨论】:

sendData 是如何被调用的? @Kamil Klimek 这是一个公共功能。 IE。每当用户/主应用程序需要发送消息时。没有任何信号/插槽 我需要知道它是如何被调用的——重要的是要知道你在哪个线程上执行 write on socket @KamilKlimek 例如:QByteArray msg = QByteArray::fromRawData("?r",2); 然后netSupport->sendData(&msg); 好的,你没有完全理解我的问题,所以我会问另一个问题 - 你是否 100% 确定,你在与套接字所在的线程相同的线程上调用 sendData 跨度> 【参考方案1】:

你在这段代码中做错了很多事情,我不知道从什么开始。

NetworkSupport::NetworkSupport(QThread* mainThread)

    libThread = mainThread;
    server.moveToThreaD(libThread);

这将无济于事。 server 已经和 MainClass 实例在同一个线程中

    server.listen(QHostAddress::Any, 5200);

服务器将开始监听与 MainClass 相同的线程

    QMetaObject::invokeMethode(&initWlan, "quit", Qt:QueuedConnection);
    initWlan.exec();

这让我很反感。这将简单地启动事件循环并几乎立即退出。



void NetworkSupport::run()
    connect(&server, SIGNAL(newConnection()), this, SLOT(acceptConnection()));

这只会在新线程中运行,在 connect 语句之后立即调用 connect 并退出线程 - 在 connect 之后,您的线程将不再运行。此外,插槽acceptConnection 可能会在与 MainClass 相同的线程中调用。我想知道 MainClass 是在何时何地创建的。

在我看来,您同时在为许多事情而苦苦挣扎。您可能在某处读到,您应该使用单独的线程进行网络通信,这样您就不会阻塞其他线程。首先尝试单线程方法。当你开始工作时,你应该考虑如何利用其他线程来满足你的需要。

额外问题:这段代码是否是某种应用程序插件,可能根本没有 Qt 事件循环?还是它是全栈 Qt 应用程序的一部分?

【讨论】:

感谢您的指导。 1)正如我所说,moveToThread()s 太多了 2)invokeMethod 正在按照你所说的故意做。它为网络启动 Signal/Slots 交互,但不会挂在.exec() 上,因为它将控制权交还给主 QEventLoop。 3)acceptConnection 运行良好,只是不是client->write()(可能更多)。但是我不在项目自动取款机前,所以当我回来时我会仔细检查这些点。奖金问题:有一个主要的 QEventLoop :),这个程序被集成到【参考方案2】:

您的代码中有一些误解 Qt 网络和多线程是如何工作的。

首先,在 Qt 文档中,他们在 QTcpServer::nextPendingConnection() 上说:

返回的 QTcpSocket 对象不能被其他线程使用。如果 你想使用来自另一个线程的传入连接,你需要 覆盖incomingConnection()。

因此,您必须创建自己的 Server 类,该类继承自 QTcpServer,并在那里覆盖 incomingConnection()。在该覆盖方法中,您必须将套接字描述符发布到您的 TCP 客户端将处理该连接的其他线程。在这个简单的例子中,我们只是发出一个信号。

signals:

   void myNewConnectionSignal(DescriptorType);

protected:

  void incomingConnection(DescriptorType descriptor)
  
      emit myNewConnectionSignal(descriptor);
  

其次,NetworkSupport 线程是干什么用的?我猜,您希望您的服务器对象在那里生活和工作?如果是这样,那么您必须以其他方式重写它。以下仅是服务器部分。请注意,QThread 已经有一个 QEventLoop,您可以通过 run() 中的 exec() 来使用它。

...
protected:
    MyServer *server;
...

NetworkSupport::NetworkSupport()

    // this is called in the main thread, so "this" lives in main thread
    server=0;
    client=0;
    // say thread to start, but it will be actually started within run()
    start();
    // sometimes it is recommended to wait here for the thread started


NetworkSupport::~NetworkSupport()

    // this is called in the main thread, say this thread to stop running
    quit();
    // wait for completion
    wait();


void NetworkSupport::run()

    // this is called in server thread
    server=new MyServer();
    if (server->listen(QHostAddress::Any, 5200))
    
      // connect our new signal to slot where we create and init the TCP client, note that Qt::QueuedConnection is used because we want acceptConnection to run in the main thread
      connect(server, SIGNAL(myNewConnectionSignal(DescriptorType)), MyClientsPool, SLOT(acceptConnection(DescriptorType)), Qt::QueuedConnection);
      // go for infinite event loop
      exec();
    
    else
    
       // do you always ignore errors?
    
    // the thread is stopped, let's destroy the server in the thread where it was born
    delete server;
    server=0;

客户端在您的主线程中生活和工作。你不能直接从其他线程调用它的方法。 NetworkSupport 也存在于主线程中:是的,它封装和管理一些其他线程,但作为 QObject 本身,它存在于我们创建它的线程中。下面的方法总是在主线程中执行,因为我们连接了服务器的信号使用 Qt::QueuedConnection 到 NetworkSupport::acceptConnection(),它告诉 Qt 我们希望在其 QObject 所在的线程中调用插槽。

   private slots:

      void socketDisconnected();

...

void NetworkSupport::socketDisconnected()

    if (client)
    
       client->deleteLater();
       client=0;
    


void NetworkSupport::acceptConnection(DescriptorType descriptor)

    QTcpSocket* client=new QTcpSocket(this);
    client->setSocketDescriptor(descriptor);
    connect(client,SIGNAL(disconnected(),this,SLOT(socketDisconnected());
    connect(client,SIGNAL(readyRead(),this,SLOT(receive());


void NetworkSupport::receive()

    if (client)
      curMessage = client->readAll();


void NetworkSupport::sendData(QByteArray* msg)

    if (client)
    
        client->write(*msg);
        client->waitForBytesWritten();
    

更新

如果我们只想隐藏线程内的所有网络工作。请注意,在下面的示例中,几乎没有错误处理和大量消息数据副本。您可能希望针对生产对其进行优化。

// private container class incapsulated and working in the thread
class NetworkThreadContainer : public QObject

     Q_OBJECT

     public:

        NetworkThreadContainer(QObject* parent=0):QObject(parent),client(0)
        
           if (server.listen(QHostAddress::Any, 5200))
            
              connect(&server, SIGNAL(newConnection()), this, acceptConnection());
            
            else
            
                // don't you want to throw exception here?
                    
        

     public slots:

         void sendDataToClient(const QByteArray& barr)
         
               if (client)
               
                   client->write(msg);
                   client->waitForBytesWritten();
               
         

        void acceptConnection()
        
            if (!client)
            
              client = server.nextPendingConnection();
              connect(client, SIGNAL(readyRead()), this, SLOT(receive()));            
            
            else
            
              // what will you do with more than one client connections or reconnections?
                    
        

       void NetworkSupport::receive()
       
           if (client)
           
              QByteArray curMessage = client->readAll();
              emit messageReceived(curMessage);
           
       

      signals:

         void messageReceived(const QByteArray&);

      public:

        QTcpClient* client;
        QTcpServer server;
;

// thread class visible to outer program
class NetworkThread : public QThread

     Q_OBJECT

     public:

        NetworkThread(QObject* parent=0):QObject(parent)
        
           start();
        

        ~NetworkThread()
        
           quit();
           wait();
        

         bool sendDataToClient(QByteArray* barr)
         
              bool ok=false;
              // async send data to container's thread
              mutex.lock();
              if (container)
              
                ok=true;
                QMetaObject::invokeMethod(
                    container,
                    "sendDataToClient",
                    Qt::QueuedConnection,
                    Q_ARG(QByteArray, *barr)
                );                             
              
              else
              
                 // container either is not ready yet or already destroyed 
              
              mutex.unlock();
              return ok;
             

     signals:

        void messageReceived(const QByteArray&);

     protected:

        void run()
        
            mutex.lock();
            // I would suggest to catch exception here and assign the error result to some variable for further processing
            container=new NetworkThreadContainer();
            mutex.unlock();
            connect(container,SIGNAL(messageReceived(QByteArray),this,SIGNAL(messageReceived(QByteArray)),Qt::QueuedConnection);
            exec();
            mutex.lock();    
            delete container;
            mutex.unlock();
           

     private:

        QMutex mutex;
        QPointer<NetworkThreadContainer> container;   
;

【讨论】:

非常感谢您的准确回答,在某些方面我不得不同意,但请给我一些时间仔细看看 @user3085931 实际上还不清楚你想在这里使用多线程实现什么。您是否希望所有网络处理(包括发送/接收客户端数据)都在单独的线程中执行?在我的回答中,我重写了一点你的代码并解释了它是如何工作的,但它并没有完全解决这个任务。 其实我不太担心它是如何实现的。我一开始没有为网络类使用线程,但在执行期间总是有这些 QThread 错误(如上所述)。我认为这种行为来自于该程序的使用方式。它(主应用程序)实际上用作更大程序的插件(不幸的是我对此一无所知)并应用多个插件,我猜设计者使用每个插件的线程。这现在(看起来)会干扰信号/插槽注册,如上所示。所以我需要告诉他们这是一个线程。 如果您想将所有内容隐藏在线程中,这比我写的要容易。我会更新它的实现方式。 我现在不在这个项目附近,但我回来后会仔细检查它。已经谢谢了。

以上是关于(QNativeSocketEngine)QObject:无法为不同线程中的父级创建子级的主要内容,如果未能解决你的问题,请参考以下文章

Qt QLine 类扩展

GUI学习之三——QObject学习总结

比特币底部正在形成?

如何从 QML 连接 C++ 对象的破坏信号?

如何从 QML 连接 C++ 对象的破坏信号?

第六课窗口组件及窗口类型