Bonjour,

Le premier exemple de cette doc encapsule un object QThread dans une classe Controller.
Du coup, ~Controller doit appeler wait() pour s'assurer que le thread est terminé, sans quoi le QThread serait détruit trop top:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Worker : public QObject
{
    Q_OBJECT
 
public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        emit resultReady(result);
    }
 
signals:
    void resultReady(const QString &result);
};
 
class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};
Sur la même page de documentation, quelques § plus tard:
Note: wait() and the sleep() functions should be unnecessary in general, since Qt is an event-driven framework. Instead of wait(), consider listening for the finished() signal.
Je suis totalement d'accord avec ça: si j'utilise un thread, c'est probablement pour ne pas freezer l'UI. Appeler wait() dans ~Controller() pourrait la freezer, donc je pense que c'est une très mauvaise idée.
Par conséquent, je pense qu'il vaut mieux utiliser un QThread* dans Controller, et connecter &QThread::finished à &QObject::deleteLater à la fois pour le worker et le workerThread:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Controller : public QObject
{
    Q_OBJECT
    QThread *workerThread;
public:
    Controller() :
            workerThread(new QThread) {
        Worker *worker = new Worker;
        worker->moveToThread(workerThread);
        connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread->start();
    }
    ~Controller() {
        workerThread->quit();
        // no need to workerThread->wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};
Ainsi, le destructeur du Controller peut terminer sans attendre (sans wait()), et le worker/workerThread seront supprimés dès que possible. Et ça fonctionne bien…

Sauf sur la terminaison du programme. Si le destructeur du Controller est dû à la terminaison du programme, alors l'event loop est en train d'être stoppée, et les slots [i]deleteLater[/] ne seront jamais appelés.

Nous pourrions dire "ce n'est pas grave, le programme se termine de toute façon", mais ça semble insatisfant. En théorie, nous pourrions quand même vouloir exécuter les destructeurs du worker/workerThread à la fin de programme (et ça éviterait à valgrind de rapporter beaucoup de fuites mémoire à cause de ça).

Qu'en pensez-vous?