3
votes

Im aware the I cannot communicate with QTcpSocket between threads but I cannot find what Im doing wrong.

As soon as I send data emitting signal to QTcpSocket which is in the other thread, QSocketNotifier should disable itself to let QTcpSocket read data. Because I get above error it will not get disabled and readyRead() signal wont be emitted.

Im using QEventLoop to implement blocking mechanism (synchronous communication - ModBus protocol) without locking the main loop. I cannot use waitReadyRead() because I had problems with that function in the past (timeouts):

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

and

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

Someone can be curious why Im setting up so many threads:

Thread1: Each new communication creates a new thread and locks it with "global" mutex to ensure that no other function will execute parallel communication. Inside that thread - after preparations and locking mutex I communicate with QTcpSocket by Qt::QueuedConnection signal which is in Thread2 (constant communication with device).

Thread2: QTcpSocket object created and moved to thread. Once I connect with the device I dont want to close the socket. Because Im blocking main event loop I need other event loop to run signals from QTcpSocket.

And the code:

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()));
}

So as I said slot TcpSocketClient::readyRead() will not be called and I get message on output: "QSocketNotifier: socket notifiers cannot be disabled from another thread".

If I replace this code:

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;
}

with:

ElfKernel::FrameReceived ControlUnit::execModbusCommand( std::list<unsigned int> paramsToCommand, FunctionStringList execCommand ) // Main Thread
{
    FrameReceived ret = execCommand( paramsToCommand );  // Main Thread
    return ret;
}

So the program is not pushed to thread but run in Main Thread (only difference!) then everything works fins - readyRead slot works loop breaks and I get the data. I need to run ModbusCommandReply as a thread because I set a mutex.lock() inside mcr->execute so whenever other part of my program run method execModbusCommand it will be stopped until other thread will free resources to establishe communication.

I dont understand why it says "QSocketNotifier: socket notifiers cannot be disabled from another thread" - i set up QTcpSocket inside THREAD_2 after the thread has started by putting new object on a heap: tcpSocket_ = new QTcpSocket(this);

I will be grateful for any help, thanks!

1

1 Answers

5
votes

In the Main Thread I have made a connection:

tcpSocket->connect( ... );

which spawn child object in Main Thread not in a tcpSocketThread.

Also it is recommended to do:

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