简体   繁体   中英

QTcpSocket in QThread will commitTransaction but when Write is called “Cannot create children for a parent that is in a different thread.”

Disclaimer: I am relatively new to Qt and any type of programming that revolves around Threads and Networking. I have also adopted a lot of code from Qt Examples, API, and other online examples.

All code can be found on GitHub . This code is relatively as simple as it can get minus striping out GUI. I figure supplying it this way would help as well versus just pasting the code below.

I want to use and believe I need to use Threads as I need multiple clients send a request to the server, the server run some SQL code, then spit out the results back to the client (basically deriving a MySQL Server, but specific to what I am doing). Right now though, I am just working on learning the workings of it all.

With all that being said, as the Title states.. My client can connect to the server, the server sets up the thread, and will receive data (a String) through the readReady. After the data is read in, for right now I am just trying to echo it back to the client. It will do this, but only once. Then it spits out:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNativeSocketEngine(0x266cca92ea0), parent's thread is serverThread(0x266cca9ed60), current thread is QThread(0x266cac772e0)

I cannot send any further data to the server unless I have the client reconnect, then after the data is sent, it will do its job but then spit out the same error and cease functioning. I have tried quite a bit of different things, but cannot seem to fix the issue. I even tried setting up a SIGNAL/SLOT for this as suggested in API:

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.

Anyway, any help would be greatly appreciated! My Code is below..

Server

ServerThread.cpp

// Project
#include "ServerDialog.h"
#include "ServerThread.h"

ServerThread::ServerThread(qintptr _socketDiscriptor, QObject *parent /*= 0*/)
    : QThread(parent)
{
    socketDiscriptor = _socketDiscriptor;
}

void ServerThread::run()
{
    emit threadStarted(socketDiscriptor);

    // Start Thread
    clientSocket = new QTcpSocket;

    // Set SocketDisc
    if (!clientSocket->setSocketDescriptor(socketDiscriptor))
    {
        emit error(clientSocket->error());
        return;
    }

    // Connect Socket and Signal
    connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(clientSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));

    //// Loop Thread to Stay Alive for Signals and Slots
    exec();
}

void ServerThread::readyRead()
{
    QDataStream in(clientSocket);
    in.setVersion(QDataStream::Qt_5_7);
    in.startTransaction();
    QString dataReceived;
    in >> dataReceived;
    if (!in.commitTransaction())
    {
        emit readyReadError(socketDiscriptor);
        return;
    }
    emit readyReadMessage(socketDiscriptor, dataReceived);
    echoData(dataReceived);
}

void ServerThread::disconnected()
{
    emit threadStopped(socketDiscriptor);
    clientSocket->disconnect();
    clientSocket->deleteLater();
    this->exit(0);
}

void ServerThread::echoData(QString &data)
{
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_5_7);
    out << data;
    clientSocket->write(block);
}

So in ServerThread.cpp when echoData is called, that is when the error shows up and the Socket ceases functioning.

Any and all help will be appreciated. I know there are a few other posts regarding "Cannot create children for..." in regards to Threads. But I did not find any of them helpful. The one thing that I did find interesting but did not understand was maybe using moveToThread() but a lot of mixed comments on that.

I learn best through code examples along with explanation versus just an explanation or pointer to API. Thank you!

Most of Qt network functions are asynchronous; they do not block the calling thread. There is no need to mess up with threads if you are using QTcpSocket s . In fact, creating a thread for every socket is an overkill, since that thread will spend most of its time just waiting for some network operation to finish. Here is how I would implement a single-threaded echo server in Qt:

#include <QtNetwork>
#include <QtCore>

//separate class for the protocol's implementation
class EchoSocket : public QTcpSocket{
    Q_OBJECT
public:
    explicit EchoSocket(QObject* parent=nullptr):QTcpSocket(parent){
        connect(this, &EchoSocket::readyRead, this, &EchoSocket::EchoBack);
        connect(this, &EchoSocket::disconnected, this, &EchoSocket::deleteLater);
    }
    ~EchoSocket() = default;

    Q_SLOT void EchoBack(){
        QByteArray receivedByteArray= readAll();
        write(receivedByteArray);
        disconnectFromHost();
    }
};

class EchoServer : public QTcpServer{
public:
    explicit EchoServer(QObject* parent= nullptr):QTcpServer(parent){}
    ~EchoServer() = default;

    //override incomingConnection() and nextPendingConnection()
    //to make them deal with EchoSockets instead of QTcpSockets
    void incomingConnection(qintptr socketDescriptor){
        EchoSocket* socket= new EchoSocket(this);
        socket->setSocketDescriptor(socketDescriptor);
        addPendingConnection(qobject_cast<QTcpSocket*>(socket));
    }

    EchoSocket* nextPendingConnection(){
        QTcpSocket* ts= QTcpServer::nextPendingConnection();
        return qobject_cast<EchoSocket*>(ts);
    }
};

int main(int argc, char* argv[]){
    QCoreApplication a(argc, argv);

    EchoServer echoServer;
    echoServer.listen(QHostAddress::Any, 9999);

    QObject::connect(&echoServer, &EchoServer::newConnection, [&](){
        EchoSocket* socket= echoServer.nextPendingConnection();
        qDebug() << "Got new connection from: " << socket->peerAddress().toString();
    });

    return a.exec();
}

#include "main.moc"

Notes:

  • This server has the ability to handle more than one client at the same time, since there is no blocking. The thread will just respond to the event that happens with the appropriate action; So, if that event was a new connection, it will create a new EchoSocket object to handle it and prints a statement out to qDebug() , and if that event was receiving something on a previously created socket, the same thread will echo received data back and close the connection. It will never block on a single connection waiting for data to arrive nor it will block waiting for a new connection to arrive .
  • Since you mention using some SQL queries in response for some connections later in your project. Please avoid threading since an SQL database connection in Qt can be used only from the thread that created it, see docs here . So, You'll have to either create a new database connection for each thread (and thus for each connection) in your application (and this is beyond just overkill), or switch later to a single threaded design.

In this section, I am explaining why threading does not work for you the way you are doing it:

You should not be declaring slots in your QThread subclass, Instead, use worker QObject s and move them to QThread s as needed .

The quote you have provided in your question is the exact explanation for why you get this warning. The ServerThread instance you create will be living in the main thread (or whatever thread that created it). Now let's consider this line from your code:

connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));

The signal readyRead() will be emitted from the current ServerThread instance (since the clientSocket object that emits it lives there), However, the receiver object is the current ServerThread instance, But that lives in the main thread . Here is what the documentation says :

If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used .

Now, the main point of Qt::QueuedConnection is executing the slot in the receiver object's thread. This means that, your slots ServerThread::readyRead() and ServerThread::disconnected will get executed in the main thread . This is most likely not what you meant to do, since you'll end up accessing clientSocket from the main thread. After that, any call on clientSocket that results in child QObject s being created will result in the warning you get (you can see that QTcpSocket::write() does this here ).

Mixed comments of movetothread are linked mostly to usage of it to move thread object to itself.

The quote hints that the members of QThread aren't designed to be called from worker. Strictly proper way to call signal would be by using worker object model, that was shown in Qt examples and explained a few times on QT-related blogs:

class Worker : public QObject
{
    Q_OBJECT
private slots:
    void onTimeout()
    {
        qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
    }
};

class Thread : public QThread
{
    Q_OBJECT

private:
    void run()
    {
        qDebug()<<"From work thread: "<<currentThreadId();
        QTimer timer;
        Worker worker;
        connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
        timer.start(1000);

        exec();
    }
};

worker constructed inside run() is "property" of the thread it created, so figuratively speaking, it is slaved to its context. The same effect maybe achieved if you create worker in other thread, then move it to this thread before connection was made. When you connect signal to slot of the QThread itself, you connect child thread to thread it was created by.

Use of

connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);

or creating connection from your thread sometimes seems to achieve proper result, but not in this case, where you try use objects constructed in different threads together. Calling moveToThread(this) in constructor is a thing not recommended to do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM