(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 【问题描述】:我遇到了一个问题,曾多次在此处询问类似情况,但我在这些主题中找不到解决方案。
我有一个主类,我想通过 qt-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:无法为不同线程中的父级创建子级的主要内容,如果未能解决你的问题,请参考以下文章