QML multithread client with c++ integration



  • Hi every one
    I have a QML Client and want to integrate it with a c++ backend. The problem is there are some loops on some of the c++ functions and they cause the UI to freeze, so I tried to move the backend to another thread, with:

    tcpHandler *tcp = new tcpHandler();
    tcp->moveToThread(TcpThread);
    TcpThread->start();
    engine.rootContext()->setContextProperty("tcpHandler", tcp);
    

    In my tcphandler class I make an object from QTCPSocket, and then move it to a differnet thread but it gave me the error: Cannot create children for a parent that is in a different thread.
    So I tried to move the QQmlApplicationEngine to another thread instead but it gave me the same error, So it seems I can't move the Engine or the socket to another thread, So what should I do to have a non freezing UI?



  • hi,
    @PouryaTorabi said in QML multithread client with c++ integration:

    assuming your long operations (loops) are inside tcp object, you can move it so a separate thread (like you did), but don't create any other qthread in that tcp object, just call your long operations (in public slots) , and use signals/slots to communicate

    tcp->moveToThread(TcpThread);
    

    then i would not do this

    engine.rootContext()->setContextProperty("tcpHandler", tcp);
    

    instead of setContextProperty("tcpHandler", tcp) you need to put the object where the tcp is created, lets say a class TcpInterface

    class TcpInterface
    {
        Q_OBJECT
    
    public:
        TcpInterface();
        Q_INVOKABLE void invok_doLongOperation(){
             emit doLongOperation();
        }
    
    signals :
        void doLongOperation();
    
    public :
        tcpHandler *tcp 
        QThread *TcpThread;
    };
    
    TcpInterface::TcpInterface()
    {
       TcpThread = new QThread();
       tcp = new tcpHandler();
       tcp->moveToThread(TcpThread);
       QObject::connect(this, &TcpInterface::doLongOperation,tcp,&tcpHandler::hevyFunctionName)
       TcpThread->start();
    }
    

    //main.cpp

    ...
    TcpInterface tcpClient;
    engine.rootContext()->setContextProperty("mytcpClient", tcpClient);
    

    //qml

    mytcpClient.invok_doLongOperation()
    


  • Thanks for your reply, but my problem is that in my loops and long operations, I should use the sockets slots like socket->write and so on, so if I don't make an instance of TCP in my class I don't have access to it. For example I want to upload some heavy files to the server so I should write the file's bytes via socket->write which will freeze the UI, for this reason I want to declare the TCP in a different thread.



  • @PouryaTorabi said in QML multithread client with c++ integration:

    TCP in a different thread.

    this is doing it,

    tcp->moveToThread(TcpThread); 
    

    please try this solution i believe this must work


  • Moderators

    Hi @PouryaTorabi and welcome,

    @LeLev is right, IIRC you can not set an object that lives in a different thread as contextProperty.

    The idea here is to make a wraper class that lives in the main thread and forwards signals and function calls to your (threaded) tcp class.



  • I tried @LeLev method, So first I made another class named backend, and registered it to my qml with:

    backend *backendHandler = new backend();
    engine.rootContext()->setContextProperty("backendHandler", backendHandler);
    

    Then in one of backend's functions I create an instance of the TCPhandler class and moved it in a new thread:

    QThread *TcpThread = new QThread();
    tcpHandler *tcp = new tcpHandler();
    tcp->moveToThread(TcpThread);
    TcpThread->start();
    

    In my tcpHandler class I create an instance of the Qtcpsocket. but again the UI froze. The part which cause it is:

    socket->write(mydata);
    

    Since mydata is some large datas. So I tried to write the threads to see if the socket's thread has change or not, and saw that the thread of the created socket is the same as the main thread but the tcpHandler is different. I tried to force the socket to move to tcpHandler's thread by using:

    socket->moveToThread(this->thread());
    

    but it gave the error: "Cannot create children for a parent that is in a different thread".
    So my problem is that the part which froze my UI is socket->write(mydata) which I can't move to another thread and must be in the main thread. The qml engine must also be in the main thread, so what option do I have?
    Actually what I want to do is to upload a large file to the server. (might be around 500mb)



  • @PouryaTorabi said in QML multithread client with c++ integration:

    in one of backend's functions I create an instance of the TCPhandler class and moved it in a new thread

    TcpThread and tcp must be members of your backend class.

    @PouryaTorabi said in QML multithread client with c++ integration:

    In my tcpHandler class I create an instance of the Qtcpsocket. but again the UI froze. The part which cause it is:
    socket->write(mydata);

    how do you call your tcps methodes from the backend ? With signal/slot connections ?

    Can you show how you're doing it please ?


  • Moderators

    @PouryaTorabi
    To avoid problems and conflicts, refrain from stack variables and constructer initialization in the class that is supposed to be moved to another thread.

    instead create a function to initialize stuff, for example void initClass() then inside your wrapper class connect the started signal of your thread to that initClass function.

    QThread *TcpThread = new QThread();
    tcpHandler *tcp = new tcpHandler();
    tcp->moveToThread(TcpThread);
    connect(TcpThread, &QThread::started, tcp, &tcpHandler::initClass);
    TcpThread->start();
    


  • @PouryaTorabi said in QML multithread client with c++ integration:

    froze

    inside your backend don't call tcps methods directly like this :

    //backend.cpp

    tcp->slotName();
    

    use signal/slot instead


  • Qt Champions 2017

    Sockets are special, you can't create them in some thread and then move them to another. Instead, you must create them in the thread that they're going to live in. So, to do that, queue a call in your worker object, which call is going to trigger the socket's creation and initialization. Something along those lines:

    class MyWorker : public QObject
    {
        Q_OBJECT
    
    public slots:
         void initSocket();
    
    private:
        QTcpSocket * socket = nullptr;
    };
    
    void MyWorker::initSocket()
    {
        socket  = new QTcpSocket(this);
        // ... More stuff ...
    }
    

    which is used like this (or equivalent):

    MyWorker * worker = new MyWorker;
    
    QThread workerThread;
    workerThread.start();
    
    worker->moveToThread(&workerThread);
    QMetaObject::invokeMethod(worker, &MyWorker::initSocket, Qt::QueuedConnection);
    

    @J.Hilk said in QML multithread client with c++ integration:

    To avoid problems and conflicts, refrain from stack variables

    Just no! Stack is harder, better, faster, stronger. ;)


  • Moderators

    @kshegunov said in QML multithread client with c++ integration:

    @J.Hilk said in QML multithread client with c++ integration:

    To avoid problems and conflicts, refrain from stack variables

    Just no! Stack is harder, better, faster, stronger. ;)

    In 99.99% of all cases I would say yes, it's true. ;-)

    But especially with QThreads I have 3 main reasons why I said that

    • (Allowed) stack size for non main threads can be (default) very limited. IIRC for MacOS is 100k
    • Many Qt classes do internal heap allocation anyway, making a stack declaration kind of moot.
    • Have you tried to do anything with a QTimer that is on the stack of a thread moved class ? Good luck with that


  • Thanks to you all, now it's working.
    First of all as you guys mentioned I can't move the socket to a new thread and should create the socket in the thread I want so I created an initSocket() function.
    To use the socket, as @LeLev said from another thread I should use the signal/slot, and it works correctly now.


  • Qt Champions 2017

    @J.Hilk said in QML multithread client with c++ integration:

    But especially with QThreads I have 3 main reasons why I said that

    • (Allowed) stack size for non main threads can be (default) very limited. IIRC for MacOS is 100k

    Half a megabyte, which is more than enough. Even 100k is large enough. Realistically no library will create a thread stack by default with less than 256k from what I've observed, but even that number is quite enough for normal work.

    • Many Qt classes do internal heap allocation anyway, making a stack declaration kind of moot.

    Actually that's one very good reason to use it. You don't want to allocate a pointer in the heap, that's such a waste (also time-wise, the stack's much, much faster). Firstly your heap is getting fragmented, and secondly would you ordinarily do:

    int * a = new int;
    *a = 20;
    

    I wouldn't think so.

    • Have you tried to do anything with a QTimer that is on the stack of a thread moved class?

    Actually, I have. There's no problem with it, just give it the proper parent.

    PS. An important note
    While I and many others use "stack" as kind of a jargon, what formally is meant is "auto-storage". Bear in mind your worker object is allocated in the heap, so a member of it is not really allocated in the stack. This:

    struct A
    {
        QByteArray x;
    };
    
    A * object = new A;
    

    Doesn't mean x is in the stack, it's still in the heap, however as you know the size of the QByteArray (and thus the full size of the structure) the heap allocation is done in one step as it'd been done on the stack. This is formally known as "auto-storage", but again, by merits of jargon we call it a "stack" allocation.


  • Moderators

    @kshegunov said in QML multithread client with c++ integration:

    Actually, I have. There's no problem with it, just give it the proper parent.

    Can you elaborate on that, when I try that I get a call to deleted constructor error.


  • Qt Champions 2017

    class Worker : public QObject
    {
        Q_OBJECT
    public:
        Worker();
    
    private:
        QTimer timer;
    };
    
    Worker::Worker()
        : timer(this)
    {
    }
    

  • Moderators

    @kshegunov oh initializerlist, not my go do method.

    Ok that works,
    but I still get QObject::startTimer: Timers cannot be started from another thread errors when calling

    void startTimer()
    {
        m_timer.start(1000);
    }
    

  • Qt Champions 2017

    Well, the message is descriptive. Don't start the timer from the wrong thread. ;)

    Either start the timer before moving it to the other thread, or queue the call over the event loop of the receiving thread.


  • Moderators

    @kshegunov
    well Kudos to you!
    Something more to be learned from this thread ;-)


Log in to reply
 

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