qt - QSocketNotifier : socket notifiers cannot be disabled from another thread

标签 qt sockets networking qtcpsocket

我知道我无法在线程之间与 QTcpSocket 通信,但我找不到我做错了什么。

一旦我向另一个线程中的 QTcpSocket 发送数据发射信号,QSocketNotifier 应该禁用自身以让 QTcpSocket 读取数据。因为我遇到上述错误,所以它不会被禁用并且不会发出 readyRead() 信号。

我使用 QEventLoop 实现阻塞机制(同步通信 - ModBus 协议(protocol))而不锁定主循环。我无法使用 waitReadyRead(),因为我过去在使用该函数时遇到过问题(超时):

https://bugreports.qt-project.org/browse/QTBUG-24451

https://bugreports.qt-project.org/browse/QTBUG-14975

有人会好奇我为什么要设置那么多线程:

Thread1:每一次新的通信都会创建一个新的线程,并用“全局”互斥量锁定它,以确保不会有其他函数执行并行通信。在该线程内 - 在准备和锁定互斥锁之后,我通过 Thread2 中的 Qt::QueuedConnection 信号与 QTcpSocket 通信(与设备持续通信)。

Thread2:QTcpSocket 对象创建并移动到线程。连接到设备后,我不想关闭套接字。因为我阻塞了主事件循环,所以我需要其他事件循环来运行来自 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->execute 中设置了一个 mutex.lock(),所以每当我的程序的其他部分运行方法 execModbusCommand 时,它将停止,直到其他线程释放资源以建立通信。

我不明白为什么它说“QSocketNotifier:无法从另一个线程禁用套接字通知程序” - 我在线程启动后通过将新对象放在堆上来在 THREAD_2 中设置 QTcpSocket:tcpSocket_ = new QTcpSocket(this);

如果有任何帮助,我将不胜感激,谢谢!

最佳答案

在主线程中我建立了一个连接:

tcpSocket->connect( ... );

在主线程中而不是在 tcpSocketThread 中生成子对象。

另外推荐做:

tpcSocket->setParent(0);
// before
tcpSocket->moveToThread( tcpSocketThread );

关于qt - QSocketNotifier : socket notifiers cannot be disabled from another thread,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16589291/

相关文章:

Matlab 套接字等待响应

Linux 阻塞 udp 套接字收不到

c# - 如何在 C# 中设置服务类型位?

Java并发网络问题

python - 从多个源 IP 发送流量 Scapy

qt - 重载qt属性

c++ - 从 QAbstractListModel 中删除项目后 QML 崩溃

c++ - 使用 Phonon 播放 mp4 视频

c++ - 无限图算法

c++ - 在 C++ 中使用 UDP 的简单选择实现