Need Help with getting a slot executed in the correct thread



  • Greetings.

    I'm trying to implement a TCP server with a thread pool. Meaning that for each incoming connection to the server the server instantiates a runnable class that handles the communications with the client until the connection is closed, this runnable is then passed to a QTheardpool class that runs it.

    I'm using QTCPSocket's signal readyRead to notify the runnable that it has to read from the socket's buffer. However, it seems that because the runnable class was instantiated in the main thread, the reading is done in the main thread itself. I would like the reading to be done in the thread that runs the runnable.

    My ServerRunnable class also inherits from QObject in order to be able to use an event loop and I used QEventLoop to prevent the run function from returning until all communications with the server are done.

    Server.cpp code:

    
    Server::Server(QTcpServer *parent) : QTcpServer(parent)
    {
    
        this->threadPool = new QThreadPool(parent);
        this->threadPool->setMaxThreadCount(8);
    
    }
    
    void Server::startServer()
    {
        qDebug() << tr("Thread %1: Starting server ...").arg(int(QThread::currentThreadId()));
    
    
        if(!this->listen(QHostAddress::LocalHost,4000)){
            qDebug() << tr("Server could not start because '%1' ").arg(this->errorString());
        }
    
        qDebug() << tr("Server started and is listening at %1:%2").arg(this->serverAddress().toString()).arg(this->serverPort());
    
    
    
    
    
    }
    
    void Server::incomingConnection(qintptr handle)
    {
    
        qDebug() << tr("Thread %1: incomingConnection funciton in Server has been called....").arg(int(QThread::currentThreadId()));
    
    
        ServerRunnable *runnable = new ServerRunnable(handle);
    
        runnable->setAutoDelete(true);          // This is true by default.
    
        this->threadPool->start(runnable);
    
    
    
    
    }
    
    
    

    serverrunnable.cpp:

    
    #include "serverrunnable.h"
    
    
    
    ServerRunnable::ServerRunnable(qintptr socketHandle)
    {
        this->socketHandel = socketHandle;
    }
    
    void ServerRunnable::run()
    {
    
        this->init();
    
    
        qDebug() << tr("Runnable %1 started. Running...").arg(int(QThread::currentThreadId()));
    
        qDebug() << tr("Runnable %1 connected to %2:%3").arg(int(QThread::currentThreadId())).arg(this->socket->peerAddress().toString()).arg(this->socket->peerPort());
    
        qDebug() << tr("Waiting for client input...");
    
    
    
        this->loop->exec();
    
        qDebug() << tr("Thread: %1: End of run funciton.").arg(int(QThread::currentThreadId()));
    
    
    
    
    
    }
    
    void ServerRunnable::init()
    {
    
    
        this->socket = new QTcpSocket();
    
        this->loop = new QEventLoop();
    
    
        this->socket->setSocketDescriptor(this->socketHandel);
    
    
        connect(this->socket,&QTcpSocket::disconnected,this,&ServerRunnable::onSocketDisconnected,Qt::QueuedConnection);
        connect(this->socket,&QTcpSocket::readyRead,this,&ServerRunnable::onReadReady,Qt::QueuedConnection);
    
    }
    
    void ServerRunnable::onReadReady()
    {
        qDebug() << tr("Thread: %1 :Got read ready signal from socket...").arg(int(QThread::currentThreadId()));
    
        QByteArray ba;
    
        ba = this->socket->readAll();
    
        qDebug() << tr("Client message: %1").arg(QString(ba));
    
        qDebug() << tr("Echoing message back to client...");
    
        this->socket->write(ba);
    
        this->socket->flush();
    
        this->socket->waitForBytesWritten();
    
        qDebug() << tr("Message sent to client...");
    
    
    
    }
    
    void ServerRunnable::onSocketDisconnected()
    {
        qDebug() << tr("Thread: %1 :Socket was disconnected... Exiting...").arg(int(QThread::currentThreadId()));
        this->shouldExit = true;
        this->socket->close();
        this->socket->deleteLater();
        this->loop->exit();
        this->loop->deleteLater();
    
    
    //    this->eventLoopQuit();
    
    
    }
    
    
    

    This is the output when I run the server, connect to it and send messages:

    "Thread 15096: Starting server ..."
    "Server started and is listening at 127.0.0.1:4000"
    "Thread 15096: incomingConnection funciton in Server has been called...."
    "Runnable 12048 started. Running..."
    "Runnable 12048 connected to 127.0.0.1:52818"
    "Waiting for client input..."
    "Thread: 15096 :Got read ready signal from socket..."
    "Client message: Hello Server\r\n"
    "Echoing message back to client..."
    QObject: Cannot create children for a parent that is in a different thread.
    (Parent is QNativeSocketEngine(0x280d938), parent's thread is QThread(0x280d828), current thread is QThread(0x2800570)
    "Message sent to client..."
    "Thread: 15096 :Got read ready signal from socket..."
    "Client message: Hello again server!\r\n"
    "Echoing message back to client..."
    "Message sent to client..."

    The main thread is 15096, and when a new connection occurs the server correctly opens a new thread 12048. However, the thread that executes the slot for readyRead is the main thread (15096) even though the connection is made in the init() function in the thread 12048.

    Can someone point me to what am I doing wrong, and how do I make the slot execute from the correct thread?

    Thank you!



  • Hello,
    I think only the run() method is executed in a different thread, but the instance of ServerRunnable remains in the main thread (where it has been created), and this is why readyRead() is called in the main thread.

    You can have the same issue with QThread, see this from documentation:
    "It is important to remember that a QThread instance lives in the old thread that instantiated it, not in the new thread that calls run(). This means that all of QThread's queued slots will execute in the old thread. Thus, a developer who wishes to invoke slots in the new thread must use the worker-object approach; new slots should not be implemented directly into a subclassed QThread."

    A solution could be to use the "worker-object" approach, using QObject::moveToThread() as described in the QThread documentation. With this solution you are moving the entire QObject's instance into a different thread, so the slots are executed in the right thread.





  • @Gojir4 said in Need Help with getting a slot executed in the correct thread:

    A solution could be to use the "worker-object" approach, using QObject::moveToThread() as described in the QThread documentation. With this solution you are moving the entire QObject's instance into a different thread, so the slots are executed in the right thread.

    Thanks, This sounds like the right solution. But given the fact that the worker object doesn't have its own event loop (exec():) how will I go about preventing it from exiting before the connection is closed?

    In my previous implementation using QThreads I just did something like this:

    void SocketThread::run()
    {
        qDebug() << tr("Thread %1 Started ...").arg(int(QThread::currentThreadId()));
    
        exec();
    
    
    }
    

    How can I achieve similar behavior in the worker object? Do I need to use QEventLoop?





  • @VRonin

    I read it, and maybe I missed something but I didn't see how to keep the worker alive after it finished running it's 'process' function.



  • the default implementation of QThread::run starts an event loop so if you use the method described in that link everything will work out of the box



  • @VRonin said in Need Help with getting a slot executed in the correct thread:

    the default implementation of QThread::run starts an event loop so if you use the method described in that link everything will work out of the box

    Indeed it does! Thanks!



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.