QThread pętla forever, przyjazne tworzenie/niszczenie

0

Witam!

Stworzyłem przykładową klasę, gdzie pętla wykonuje się w wątku:

// idea wątku z nieskończoną pętlą.
#ifndef QWORKER_H
#define QWORKER_H

#include <QObject>
#include <QThread>
#include <QEventLoop>
#include <QTimer>
#include <QMutex>
#include <QDebug>

class QWorker : public QObject
{
    Q_OBJECT
public:
    explicit QWorker(QObject *parent = 0) :
        //QObject(parent), // będziesz sierotą
        QObject(),
        mThread(),
        mTerminate(false)
    {
        qDebug() << "Parent thread" << parent->thread()->currentThreadId();
        this->moveToThread(&mThread);

        connect(&mThread, SIGNAL(started()), this, SLOT(doWork()));
        mThread.start();
    }

    ~QWorker(){
        qDebug() << __FUNCTION__ << thread()->currentThreadId();
        abort();
        mThread.quit();
        mThread.wait();
    }

    void abort(){
        QMutexLocker ml(&mMutex);
        mTerminate = true;
    }

private:
    QThread mThread;
    bool    mTerminate;
    QMutex  mMutex;
    /* ... */

signals:

public slots:
    void doWork(){
        qDebug() << __FUNCTION__ << "enter";
        forever{

            mMutex.lock();
            if(mTerminate){
                qDebug() << __FUNCTION__ << "Terminate";
                break;
            }
            mMutex.unlock();

            /* ... */

            qDebug() << __FUNCTION__ << "looping" << thread()->currentThreadId();
#if 1
            QEventLoop loop;
            QTimer::singleShot(1000, &loop, SLOT(quit()));
            loop.exec();
#else
            mThread.sleep(1);
#endif


        }
        mMutex.unlock();
        qDebug() << __FUNCTION__ << "exit";
    }

    void doMethod(){
        QMutexLocker ml(&mMutex);
        /* ... */
        qDebug() << __FUNCTION__ << thread()->currentThreadId();
    }
};



#endif // QWORKER_H

Tworzenie oraz testy robię tak:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    mWorker = new QWorker(this);

    connect(ui->pushButton_1, SIGNAL(clicked()), mWorker, SLOT(doMethod()));
    connect(ui->pushButton_2, SIGNAL(clicked()), mWorker, SLOT(doMethod()), Qt::DirectConnection);
}
MainWindow::~MainWindow()
{
    delete mWorker;
    delete ui;
}

Mocno nie chciałbym w MainWindow zawierać kodu który wrzuca "Workera" do wątku, więc zaimplementowałem to w konstruktorze Workera.

Działanie:

Parent thread 0x7f922da7f780
Object // klasa opisana w dalszej części tematu
doWork enter
doWork looping 0x7f9221646700
doWork looping 0x7f9221646700
doWork looping 0x7f9221646700
doMethod 0x7f922da7f780 // ui->pushButton_2 - Qt::DirectConnection
doMethod 0x7f9221646700 // ui->pushButton_1 - a tego się nie spodziewałem ;) Fajnie ;)
doWork looping 0x7f9221646700
~QWorker 0x7f922da7f780
doWork Terminate
doWork exit
~Object

Moje pytania
1. Jak wywołać destruktora Workera przy zamykaniu aplikacji?
Oczywiście z pozbyciem się:

MainWindow::~MainWindow()
{
    //delete mWorker; // !!!!!!!!!!!
    delete ui;
}

Podoba mi się takie "inteligentne" niszczenie obiektów:

#ifndef OBJECT_H
#define OBJECT_H

#include <QObject>
#include <QDebug>

class Object : public QObject
{
    Q_OBJECT
public:
    explicit Object(QObject *parent = 0) :
        QObject(parent)
    {
        qDebug() << __FUNCTION__;
    }

    ~Object(){
        qDebug() << __FUNCTION__;
    }

signals:

public slots:

};

#endif // OBJECT_H

Gdzie utworzenie za pomocą:

new Object(this);

przechowuje informację, że jeżeli rodzic zginie, dzieciaki też poumierają:

# start aplikacji
Object
# kliknięcie X aplikacji ;)
~Object

Worker nie może mieć rodzica, ponieważ jest przenoszony do nowego wątku, co więc zrobić?

2. Czy klasa QWorker jest poprawnie napisana?
-czy QWorker jest dobrze zabijany?
-czy QThread jest poprawnie zamykany?

3. Czy obie formy są poprawne w wątku?

#if 1
            QEventLoop loop;
            QTimer::singleShot(1000, &loop, SLOT(quit()));
            loop.exec();
#else
            mThread.sleep(1);
#endif

Nie będę tego stosował (ponieważ w wątku będzie odpalona niskopoziomowa biblioteka), ale warto wiedzieć ;)

4. Jak to się dzieje, że metoda doWork pracuje w wątku QWorker
Jeżeli wykonam sygnał z przycisku pushButton_1, mam wynik:

doMethod 0x7f4744dbb700
doWork looping 0x7f4744dbb700

Rozumiem, że jeśli w nieskończonej pętli mam jakieś wytchnienie w postaci sleepa, to QThread ma szanse pozbierać z kolejki żądania i powywoływać sloty w Workerze, tak?
Jeśli zapewnie, że metody będą wykonywane na tym samym wątku co "doWork", to mogę wywalić mutex'y?
Jeśli nieskończona pętla nie będzie dawać wytchnienia, to metody muszą być wykonywane bezpośrednio (Qt::DirectConnection) ?

P.S.
Staram się zrozumieć wątki w QT tak aby nie dziedziczyć po "run", bo krzyczycie na mnie i dostrzegam sens ;)
Chciałbym się upewnić że wszystko w 100% rozumiem.

0

Punkt 1.

#ifndef QWORKER_H
#define QWORKER_H

#include <QObject>
#include <QThread>
#include <QEventLoop>
#include <QTimer>
#include <QMutex>
#include <QDebug>

// nakladka, widac kiedy QMyThread jest niszczony
class QMyThread : public QThread
{
    Q_OBJECT
public:
    explicit QMyThread(QObject *parent = 0) :
        QThread(parent)
    {
        qDebug() << __FUNCTION__;
    }

    ~QMyThread(){
        qDebug() << __FUNCTION__;
    }
};

class QWorker : public QObject
{
    Q_OBJECT
public:
    explicit QWorker(QObject *parent = 0) :
        //QObject(parent), // będziesz sierotą
        QObject(),
        mThread(),
        mTerminate(false)
    {
        qDebug() << "Parent thread" << parent->thread()->currentThreadId();
        this->moveToThread(&mThread);

        connect(parent, SIGNAL(destroyed()), this, SLOT(destroyedEvent()), Qt::DirectConnection); /** DODATEK **/
        connect(&mThread, SIGNAL(started()), this, SLOT(doWork()));
        mThread.start();
    }

    ~QWorker(){
        qDebug() << __FUNCTION__ << thread()->currentThreadId();
        abort();
        mThread.quit();
        mThread.wait();
    }

    void abort(){
        QMutexLocker ml(&mMutex);
        mTerminate = true;
    }

private:
    QMyThread mThread; /** ZMIANA **/
    bool    mTerminate;
    QMutex  mMutex;
    /* ... */

signals:

private slots:
    void destroyedEvent(){
        delete this; /** wyglada idiotycznie **/
    }

public slots:
    void doWork(){
        qDebug() << __FUNCTION__ << "enter";
        forever{

            mMutex.lock();
            if(mTerminate){
                qDebug() << __FUNCTION__ << "Terminate";
                break;
            }
            mMutex.unlock();

            /* ... */

            qDebug() << __FUNCTION__ << "looping" << thread()->currentThreadId();
#if 1
            QEventLoop loop;
            QTimer::singleShot(1000, &loop, SLOT(quit()));
            loop.exec();
#else
            mThread.sleep(1);
#endif


        }
        mMutex.unlock();
        qDebug() << __FUNCTION__ << "exit";
    }

    void doMethod(){
        QMutexLocker ml(&mMutex);
        /* ... */
        qDebug() << __FUNCTION__ << thread()->currentThreadId();
    }
};

#endif // QWORKER_H

Czyli dopisane i połączone:

private slots:
    void destroyedEvent(){
        delete this; /** wyglada idiotycznie **/
    }
        connect(parent, SIGNAL(destroyed()), this, SLOT(destroyedEvent()), Qt::DirectConnection); /** DODATEK **/

Rozwiązanie dziwne, ale działa OK:

QMyThread
Parent thread 0x7ff1fb2dc780
doWork enter
doWork looping 0x7ff1eeea7700
doWork looping 0x7ff1eeea7700
~QWorker 0x7ff1fb2dc780
doWork Terminate
doWork exit
~QMyThread

Może być?

1 użytkowników online, w tym zalogowanych: 0, gości: 1