我知道我无法在线程之间与QTcpSocket通信,但我找不到我做错了什么。
一旦我将数据发送信号发送到另一个线程中的QTcpSocket,QSocketNotifier应该禁用它自己让QTcpSocket读取数据。因为我遇到上述错误,所以不会被禁用,并且不会发出readyRead()信号。
我使用QEventLoop实现阻塞机制(同步通信 - ModBus协议)而不锁定主循环。我不能使用waitReadyRead()因为我以前遇到过该函数的问题(超时):
https://bugreports.qt-project.org/browse/QTBUG-24451
和
https://bugreports.qt-project.org/browse/QTBUG-14975
有人可能好奇为什么我设置了这么多线程:
Thread1:每个新通信都会创建一个新线程,并使用“全局”互斥锁将其锁定,以确保没有其他函数执行并行通信。在该线程内部 - 在准备和锁定互斥锁之后,我通过Qt :: QueuedConnection信号与QTcpSocket通信,该信号位于Thread2中(与设备持续通信)。
Thread2:创建QTcpSocket对象并移动到线程。一旦我连接设备,我不想关闭插座。因为Im阻塞主事件循环,我需要其他事件循环来运行来自QTcpSocket的信号。
代码:
void someFunctionThatWantToSendData( void ) // Main Thread
{
ElfKernel::KernelSingleton::Instance().getGUIcontrol()->getCtrlUnitPtr()->execModbusCommand( someParameters, someCommunicationFunction );
}
ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
ModbusCommandReply* mcr = new ModbusCommandReply(); // THREAD_1!!!!!! this object run in a Thread -> ModbusCommandReply : public QThread
auto timeout = ElfKernel::CommunicationManagerSingleton::Instance().getTimeout();
mcr->execute( paramsToCommand, execCommand ); // executes THREAD_1
bool hasFinishedWithoutTimeout = mcr->wait(timeout); // waiting for THREAD_1 to finish
FrameReceived ret = mcr->getData();
delete mcr;
return ret;
}
// this method is executed after preparations done by: mcr->execute( paramsToCommand, execCommand );
ElfKernel::FrameReceived CommunicationManager::getData( std::string data ) // THREAD_1
{
emit signalTcpSocketWriteData( data );
loop.exec(); // QEventLoop declared in a header; program loops until QTcpSocket::readyRead() signal will hit the slot TcpSocketClient::readyRead()
FrameReceived fr = tcpSocket->readDataString();
}
// executed by: emit signalTcpSocketWriteData( data );
void TcpSocketClient::writeDataSlot( std::string data ) // THREAD_2!!!!!
{
tcpSocket_->write( data );
tcpSocket_->flush();
}
void TcpSocketClient::readyRead( void ) // should be executed in THREAD_2 but signal readyRead() never arrives here because a get error on Output: "QSocketNotifier: socket notifiers cannot be disabled from another thread"
{
ElfKernel::CommunicationManagerSingleton::Instance().loop.quit(); // breaks loop.exec() in CommunicationManager::getData()
}
// constructor
CommunicationManager::CommunicationManager( void ) // Main Thread
{
tcpSocket = new TcpSocketClient();
tcpSocketThread = new QThread();
tcpSocket->moveToThread( tcpSocketThread );
QObject::connect( tcpSocketThread, SIGNAL(started()), tcpSocket, SLOT(prepare()) );
tcpSocketThread->start(); // CREATED THREAD_2!!!!!
QObject::connect(this, SIGNAL(signalTcpSocketWriteData(std::string)), tcpSocket, SLOT(writeDataSlot(std::string)), Qt::QueuedConnection );
QObject::connect(this, SIGNAL(signalTcpSocketReadData(std::string *)), tcpSocket, SLOT(readDataSlot(std::string *)), Qt::QueuedConnection);
// and other stuff
}
// constructor
TcpSocketClient::TcpSocketClient(QObject *parent) // Main Thread - before it is moved to THREAD_2
{
parent;
//prepare(); // it is executed after signal QThread::started() is emitted
}
// method run after thread started
void TcpSocketClient::prepare( void ) // THREAD_2
{
tcpSocket_ = new QTcpSocket(this);
QObject::connect(tcpSocket_, SIGNAL(disconnected()), this, SLOT(hostHaveDisconnected()));
QObject::connect(tcpSocket_, SIGNAL(readyRead()), this, SLOT(readyRead()));
}
因为我说插槽TcpSocketClient :: readyRead()将不会被调用并且我在输出中得到消息:“QSocketNotifier:无法从另一个线程禁用套接字通知程序”。
如果我替换此代码:
ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
ModbusCommandReply* mcr = new ModbusCommandReply(); // THREAD_1!!!!!! this object run in a Thread -> ModbusCommandReply : public QThread
auto timeout = ElfKernel::CommunicationManagerSingleton::Instance().getTimeout();
mcr->execute( paramsToCommand, execCommand ); // executes THREAD_1
bool hasFinishedWithoutTimeout = mcr->wait(timeout); // waiting for THREAD_1 to finish
FrameReceived ret = mcr->getData();
delete mcr;
return ret;
}
使用:
ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
FrameReceived ret = execCommand( paramsToCommand ); // Main Thread
return ret;
}
所以程序没有被推送到主线程但只在主线程中运行(只有差异!)然后一切正常工作 - readyRead槽工作循环中断,我得到数据。 我需要将ModbusCommandReply作为线程运行,因为我在mcr-> gt执行中设置了一个mutex.lock(),所以每当我的程序的其他部分运行方法execModbusCommand时,它将被停止,直到其他线程将释放资源来建立通信。
我不明白为什么它说“QSocketNotifier:套接字通知程序无法从另一个线程禁用” - 我在线程启动后通过将新对象放在堆上来在THREAD_2内设置QTcpSocket:tcpSocket_ = new QTcpSocket(this); < / p>
我将不胜感激,谢谢!
答案 0 :(得分:4)
在主线程中我建立了连接:
tcpSocket->connect( ... );
在主线程中生成子对象而不是在tcpSocketThread中。
另外建议:
tpcSocket->setParent(0);
// before
tcpSocket->moveToThread( tcpSocketThread );