How to access data from thread two, created in thread one



  • Hey,

    currently I'm writing a Qt Application to control my 3D-LED Cube, all Animation are created on Desktop site and send byte by byte to the µC. However, to insure that the GUI is responsive to the user input. I created two worker threads one for generating the animation end the other is responsibly for sending the data to the LED Cube.
    The big challence is now how the get the data from the class animation which is created when the class animationThread gets called. Both are running continuously, until the user presses the pause button, closes the port or quit the application.

    Here are the two worker classes.

    @class Animations;
    class AnimationThread : public QObject
    {
    Q_OBJECT
    public:
    explicit AnimationThread(QObject *parent = 0);
    bool isRunning(){
    return m_running;
    }

    signals:
    protected:
    public Q_SLOTS:
    void createAnimations(void);
    void stop();
    private Q_SLOTS:
    private:
    // QMutex m_mutex;
    // QMutexLocker *locker;
    bool m_stoped;
    bool m_running;
    // QByteArray *sendArray;

    // The class where the animation actually are generated which inherits class Draw
    Animations *animations;
    QReadWriteLock locker;
    };

    AnimationThread::AnimationThread(QObject *parent):
    QObject(parent),
    m_stoped(false),
    m_running(false),
    animations(new Animations) // here are the animation generated and stored in a QByteArray
    {

    }
    @

    @class SendThread : public QObject
    {
    Q_OBJECT
    public:
    explicit SendThread(QSerialPort *serial, QObject *parent = 0);
    bool isRunning(void){
    return m_running;
    }

    signals:

    public Q_SLOTS:
    void sendAnimations();
    void stop();
    private:
    QSerialPort *m_uart;
    bool m_stoped;
    bool m_running;
    QReadWriteLock lock;
    };@

    If I forgot something important let me know it!
    Thanks in advance!



  • Hi,
    The easiest and safest way is using the SIGNAL - SLOT mechanism. Emit a signal with your QByteArray as parameter in animation thread and connect to a slot taking a QByteArray as parameter in your sent thread. You may also want to look at the connection options (especially Qt::DirectConnection or Qt::QueuedConnection).
    The other alternative is to give a pointer to your data in animation thread to your sand thread but be careful with data access (see QMutex for this).



  • bq. The other alternative is to give a pointer to your data in animation thread to your sand thread but be careful with data access (see QMutex for this).

    Well, I tried this but this didn't work, maybe I did it wrong :-)

    How I did it:

    @
    // In MainWindow
    QByteArray *byteArray
    aThread = animationThread(byteArray);
    sThread = sendThread(serial,byteArray);
    @

    In the animationThread the address to byteArray is passed to
    to the animation class
    On runtime I got a warning something like cannot create Child in parent or so. However it did not work!
    Maybe because the animation class does not inherit QObject?

    @class Animations : public Draw
    {
    public:
    Animations();

    // QByteArray getSendCube(void);
    /************************************************************
    * EFFECTS FUNCTION PROTOTYPES
    ************************************************************/
    void effectWall(Axis axis, Direction direction, uint16_t speed);
    void effectTest(void);
    void effectRain(uint16_t iterations,uint16_t speed);
    void effectLift(uint16_t iterations, uint16_t speed, uint16_t delay);
    void effectRandomZLift(uint16_t iterations, uint16_t speed);
    void effectFirework(uint16_t iterations, uint16_t speed, uint8_t particles);
    void effectWireBoxCornerShrinkGrow(uint16_t iterations, uint16_t speed, uint8_t rotate,
    uint8_t flip);
    void effectWireBoxCenterShrinkGrow(uint16_t speed, Bool centerStart);
    void effectAxisNailWall(uint16_t speed, Axis axis, Bool invert);
    void effectLoadbar(uint16_t speed, Axis axis);
    void effectRandomSparkFlash(uint16_t iterations, uint16_t speed,
    uint16_t bixels);
    void effectRandomSpark(uint16_t sparks, uint16_t speed);
    void effectRandomFiller(uint16_t speed, BixelState state);
    void effectString(uint8_t *str, uint16_t speed);
    // void ripples (int iterations, int delay);
    //float distance2d (float x1, float y1, float x2, float y2);

    private:
    void animationWait(uint16_t speed);
    void sendBixelZ(uint8_t x, uint8_t y, uint8_t z, uint16_t speed);
    void effectZUpDownMove(uint8_t destination[CUBE_ARRAY_SIZE],
    uint8_t position[CUBE_ARRAY_SIZE], Axis axe);

    // uint8_t sendCube[CUBE_SIZE][CUBE_SIZE];
    QTimer *timer;
    // QByteArray *sendData;
    // QReadWriteLock lock;
    // bool stop;
    };
    @

    @class Draw
    {
    public:
    Draw();
    /**
    * @brief Boolean Type definition
    */

    /***********************************************************************
     *  @brief  Two dimensional array for cube data
     *  cubeFrame[z][y] |= (0x01 << 0) ; where z=y=0
     *  which turns on the first LED at position 000 (x,y,z - Coordinate)
     *  or in other words first layer, first data bit, and first row is
     *  activated.
     ***********************************************************************/
    uint8_t cubeFrame[CUBE_SIZE][CUBE_SIZE];   // [z][y]
    uint8_t cubeFrameTemp[CUBE_SIZE][CUBE_SIZE]; // [z][y]
    
    
    
    void setBixel(uint8_t x, uint8_t y, uint8_t z);
    void setTempBixel(uint8_t x, uint8_t y, uint8_t z);
    void clearBixel(uint8_t x, uint8_t y, uint8_t z);
    void clearTempBixel(uint8_t x, uint8_t y, uint8_t z);
    BixelState getBixelState(uint8_t x, uint8_t y, uint8_t z);
    void flipBixels(uint8_t x, uint8_t y, uint8_t z);
    void alterBixel(uint8_t x, uint8_t y, uint8_t z, BixelState state);
    
    Bool inRange(uint8_t x, uint8_t y, uint8_t z);
    void shift(Axis axis, Direction direction);
    void checkArgumentOrder(uint8_t from, uint8_t to, uint8_t *newStartPoint,
                            uint8_t *newEndPoint);
    void drawPositionAxis(Axis axis, uint8_t position[CUBE_ARRAY_SIZE], Bool invert);
    uint8_t flipByte(uint8_t byte);
    
    void setPlaneZ(uint8_t z);
    void clearPlaneZ(uint8_t z);
    void setPlaneX(uint8_t x);
    void clearPlaneX(uint8_t x);
    void setPlaneY(uint8_t y);
    void clearPlaneY(uint8_t y);
    void setPlane(Axis axis, uint8_t i);
    void clearPlane(Axis axis, uint8_t i);
    
    void boxWireframe(uint8_t x1, uint8_t y1, uint8_t z1, uint8_t x2,
                      uint8_t y2, uint8_t z2);
    void boxFilled(uint8_t x1, uint8_t y1, uint8_t z1, uint8_t x2, uint8_t y2,
                   uint8_t z2);
    void boxWalls(uint8_t x1, uint8_t y1, uint8_t z1, uint8_t x2, uint8_t y2,
                  uint8_t z2);
    void mirrorX(void);
    void mirrorY(void);
    void mirrorZ(void);
    // @brief Function Prototypes for filling cube array
    void fillTempCubeArray(uint8_t pattern);
    void fillCubeArray(uint8_t pattern);
    
    uint8_t byteline(uint8_t start, uint8_t end);
    void tmpCubeToCube(void);
    void fontGetChar(uint8_t chr, uint8_t dst[5]);
    

    private:
    void wait();
    };
    @



  • bq. The easiest and safest way is using the SIGNAL – SLOT mechanism. Emit a signal with your QByteArray as parameter in animation thread and connect to a slot taking a QByteArray as parameter in your sent thread.

    Sounds promising, but there is a problem the animation generation takes some time because every animation waits at least ones for X milliseconds before it continues creating the next part of the animation, so in fact I have to emit a signal within the animation class
    because when it returns to the animationThread the data are already X times overwritten.



  • I get the following warning (error) on runtime.

    @QObject::connect: Cannot queue arguments of type 'QByteArray&'
    (Make sure 'QByteArray&' is registered using qRegisterMetaType().)
    @

    How can I solve this!



  • I get this warning when calling the sendAnimations function in SendThread.

    @QObject: Cannot create children for a parent that is in a different thread.
    (Parent is QSerialPort(0x151ecd0), parent's thread is QThread(0x14be8f0), current thread is QThread(0x7fff1a0dda50)
    @

    What does that mean should I open the serial port in the SendThread instead in MainWindow class?


  • Lifetime Qt Champion

    Hi,

    bq. // In MainWindow
    QByteArray *byteArray
    aThread = animationThread(byteArray);
    sThread = sendThread(serial,byteArray);

    That kind of construct is not safe at all.

    Since you will be sending QByteArrays in your SenderThread, just emit one from your AnimationThread



  • Well, I cant emit from AnimationaThread (I think at least :-) ), from this class all animation are called which are in the class animation.
    In each animation the wait function gets called at least ones.
    where than I emit currently a signal connected to the SenderThread.

    I changed QByteArray to QVector< QVector<uint8_t> >

    That's how I do it right now
    @// in MainWindow create connection
    connect(playAction,&QAction::triggered,animationThread,&AnimationThread::createAnimations);
    connect(animations,&Animations::dataChanged,sendThread,&SendThread::sendAnimations);
    // How the worker threads are right now created
    animations = new Animations;
    animations->moveToThread(&aThread);
    animationThread = new AnimationThread(animations);
    animationThread->moveToThread(&aThread);
    aThread.start();
    serial->moveToThread(&sThread);
    sendThread = new SendThread(serial);
    sendThread->moveToThread(&sThread);
    sThread.start();
    @

    @ // In animationThread call animation from class animation
    animationFirework(....);
    @

    @// in the animation class
    void animationFirework(...)
    {
    ...
    wait(ms);
    ....
    wait(ms);
    }

    void wait(uint16_t ms)
    {
    emit dataChanged(cubeVec); // void dataChanged(QVector< QVector<uint8_t> > &cubeVec)
    timer->start(ms)
    while(timer->isActive());
    }
    @

    @ // in class SenderThread
    void SendThread::sendAnimations(QVector< QVector<uint8_t> > &data)
    //void SendThread::sendAnimations()
    {
    // QReadLocker locker(&lock);

    // while(true){
    m_serial->putChar(0xFF);
    m_serial->waitForBytesWritten(1000);
    m_serial->putChar(0x00);
    m_serial->waitForBytesWritten(1000);
    for (int z = 0; z < CUBE_SIZE; z++) {
    for (int y = 0; y < CUBE_SIZE; y++) {
    m_serial->putChar(data[z][y]);
    m_serial->waitForBytesWritten(1000);
    if(data[z][y] == 0xFF){
    m_serial->putChar(0xFF);
    m_serial->waitForBytesWritten(1000);
    }
    }
    }
    if (m_stoped)
    return;
    // m_running = true;
    }
    @

    And get the following warrning(error)
    QObject::connect: Cannot queue arguments of type 'QVector<QVector<uint8_t> >&'
    (Make sure 'QVector<QVector<uint8_t> >&' is registered using qRegisterMetaType().)



  • Ok the problem with qRegisterMetaType() is solved



  • You can also Mutexes / ReadWriteLocks to guard your data.

    I suggest creating a helper template class, something like "GuardedData<class T>". In there you have your actual data member of type T, and a ReadWriteLock. You only provide two public methods, "T get()" and "set(T val)". Inside the get()mMethod, you lock the ReadWriteLock for reading, and inside the set()-method, you look it for writing...

    Having this helper-class, you could easily create a "GuardedData<QByteArray> m_guardedByteArray" member, that both threads can use. Via the ReadWriteLock, accesses are safe (and serialized).

    There can obviously be a performance penalty, but depending on the size of your data and the frequency of access, it might not be noticable at all...

    You can of still use the signal-slot-approach...



  • I have created this struct in a global header file and want to use it for singal and slots. But it does not work!
    In Global.hpp
    @typedef struct{
    QString name;
    QString text;
    uint8_t id;
    uint8_t particle;
    uint16_t speed;
    uint16_t delay;
    uint16_t leds;
    uint16_t iteration;
    Direction direction;
    Axis axis;
    Bool invert;
    BixelState state;
    }AnimationStruct;
    Q_DECLARE_METATYPE(AnimationStruct)@

    In MainWindow.cpp

    @ qRegisterMetaType<AnimationStruct>("AnimationStruct");@

    Error:
    @ no matching function for call to 'QObject::connect(const Object*&, void (MainWindow::&)(const AnimationStruct&), const Object&, void (AnimationThread::*&)(const AnimationStruct&), Qt::ConnectionType)'
    return connect(sender, signal, sender, slot, Qt::DirectConnection);

    invalid use of incomplete type 'struct QtPrivate::QEnableIf<false, QMetaObject::Connection>'

    declaration of 'struct QtPrivate::QEnableIf<false, QMetaObject::Connection>'
    template <bool B, typename T = void> struct QEnableIf;
    @

    EDIT: Solved



  • bq. There can obviously be a performance penalty, but depending on the size of your data and the frequency of access, it might not be noticable at all…

    Well I have currently massive performance problems using signal ans slots.
    The CPU usage is > 25% and the animation are played slower than the microcontroller would do.



  • I probably know why there is such a huge performance problem, there is anywhere in my Threads a massive memory leak.
    When I start the application it needs 14kB memory, when I start the animation Thread and sending Thread. The memory usage climbs over 1GB within 10-30 seconds :-( !
    I dont understand it.


Log in to reply
 

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