Qthread: esempio funzionante



  • Buongiorno.
    Avendo avuto non poche difficoltà a capire come far funzionare un modulo QThread, ho prodotto un esempio funzionante che allego per agevolare il lavoro di altri.
    Ops: mi dice che non sono abilitato ad effettuare upload. Provo ad aggiungere qui i pochi moduli dell'esempio.
    Questo è il MainWindow: banalissimo, 2 QTextEdit più 2 QPushButton.
    0_1502099313686_Schermata del 2017-08-07 11-47-59.png
    e questi sono tutti i listati:

    -----------------------------------------------------------------------------------
    
    // Source: main.cpp
    
    #include "mainwindow.h"
    #include <QApplication>
    
    int glob_Contatore = 0;
    bool glob_SwEsegui = false;
    double glob_inizio = 0;
    double glob_fine = 0;
    MainWindow *ptr_mainwindow;
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        ptr_mainwindow = new MainWindow;
        ptr_mainwindow->show();
    
        return a.exec();
    }
    
    
    
    
    
    -----------------------------------------------------------------------------------
    
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QString>
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
        void aggiungi_contatore();
    
    private slots:
        void on_pushButton_clicked();
    
        void on_pushButton_2_clicked();
    
        void errorString (QString);
    
    private:
        Ui::MainWindow *ui;
    };
    
    #endif // MAINWINDOW_H
    
    
    
    
    
    -----------------------------------------------------------------------------------
    
    // Source: MainWindow.cpp
    
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include "mio_qthread_vuoto.h"
    #include "modulo_loop_eterno.h"
    #include <iostream>
    
    using namespace std;
    
    extern int glob_Contatore;
    extern bool glob_SwEsegui;
    extern double glob_inizio;
    extern double glob_fine;
    extern MainWindow *ptr_mainwindow;
    
    Mio_QThread_vuoto       *istanza_QThread;
    Modulo_Loop_Eterno      *istanza_Modulo_Loop;
    
    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
        ui->setupUi(this);
    
    
        istanza_QThread = new Mio_QThread_vuoto;
        istanza_Modulo_Loop = new Modulo_Loop_Eterno;
    
        istanza_Modulo_Loop->moveToThread(istanza_QThread);
    
        QObject::connect(istanza_Modulo_Loop, SIGNAL (error(QString)), this, SLOT (errorString(QString)));
        QObject::connect(istanza_QThread, SIGNAL (started()), istanza_Modulo_Loop, SLOT (EseguiThread()));
        QObject::connect(istanza_Modulo_Loop, SIGNAL (finished()), istanza_QThread, SLOT (quit()));
        QObject::connect(istanza_Modulo_Loop, SIGNAL (finished()), istanza_Modulo_Loop, SLOT (deleteLater()));
        QObject::connect(istanza_QThread, SIGNAL (finished()), istanza_QThread, SLOT (deleteLater()));
    
        istanza_QThread->start();
    
    
    }
    
    
    MainWindow::~MainWindow() {
        delete ui;
    }
    
    
    void MainWindow::errorString (QString)
    {
        cout << "AAAAAAAAAAAAAAAAAAAAAAAA: HA FATTO ERRORE" << endl;
    }
    
    
    void MainWindow::aggiungi_contatore() {
        ui->textEdit_2->append(QString::number(glob_Contatore));
    }
    
    
    void MainWindow::on_pushButton_clicked() {
        if (glob_SwEsegui == false) {
            glob_SwEsegui = true;
            cout << "SwEsegui impostato a TRUE" << endl;
            ui->pushButton->setText("Sospendi");
        } else {
            glob_SwEsegui = false;
            cout << "SwEsegui impostato a FALSE" << endl;
            ui->pushButton->setText("Avvia");
        }
    }
    
    
    void MainWindow::on_pushButton_2_clicked()
    {
        QCoreApplication::quit();
    }
    
    
    
    
    
    -----------------------------------------------------------------------------------
    
    #ifndef MIO_QTHREAD_VUOTO_H
    #define MIO_QTHREAD_VUOTO_H
    
    #include <QThread>
    
    class Mio_QThread_vuoto : public QThread
    {
        Q_OBJECT
    
    public:
        explicit Mio_QThread_vuoto(QThread *parent = nullptr);
    
    signals:
    
    public slots:
    
    };
    
    #endif // MIO_QTHREAD_VUOTO_H
    
    
    
    
    
    -----------------------------------------------------------------------------------
    
    // Source: Mio_QThread_vuoto.cpp
    
    #include "mio_qthread_vuoto.h"
    
    Mio_QThread_vuoto::Mio_QThread_vuoto(QThread *parent) : QThread(parent)
    {
    
    }
    
    
    
    
    
    -----------------------------------------------------------------------------------
    
    #ifndef MODULO_LOOP_ETERNO_H
    #define MODULO_LOOP_ETERNO_H
    
    #include <QObject>
    
    class Modulo_Loop_Eterno : public QObject
    {
        Q_OBJECT
    public:
        explicit Modulo_Loop_Eterno(QObject *parent = nullptr);
    
    signals:
        void finished();
        void error(QString err);
    
    public slots:
        void EseguiThread();
    
    };
    
    #endif // MODULO_LOOP_ETERNO_H
    
    
    
    
    
    -----------------------------------------------------------------------------------
    
    // Source: Modulo_Loop_Eterno.cpp
    
    #include "modulo_loop_eterno.h"
    #include "mainwindow.h"
    #include <iostream>
    #include <sys/time.h>
    
    using namespace std;
    
    extern int glob_Contatore;
    extern bool glob_SwEsegui;
    extern double glob_inizio;
    extern double glob_fine;
    extern MainWindow *ptr_mainwindow;
    
    Modulo_Loop_Eterno::Modulo_Loop_Eterno(QObject *parent) : QObject(parent) {
    
    }
    
    
    void Modulo_Loop_Eterno::EseguiThread() {
    
        timeval tempo;
    
        cout << "Thread caricato" << endl;
    
        while (true) {
            if (glob_SwEsegui == true) {
                glob_Contatore++;
    
                cout << "Contatore: " << glob_Contatore << endl;
    
                ptr_mainwindow->aggiungi_contatore();
    
    loop:
                gettimeofday(&tempo,NULL);
                glob_fine = (tempo.tv_sec * 1000000 + tempo.tv_usec) / 1000;
                if (glob_inizio == 0) glob_inizio = glob_fine;
                while (glob_fine - glob_inizio < 1000) goto loop;
                glob_inizio = glob_fine;
            }
        }
    }
    


  • Tuttle le variabili globali sono race conditions in questo esempio e hai pure un potenziale memory leak del thread (se chiudi la finestra prima che il thread finisca tutto crasha).

    Non puoi usare direttamente cose che non vivono sullo stesso thread. O proteggi l'accesso in qualche modo (std::atomic QMutex, QReadWriteLock, etc.) o, come sarebbe meglio, deleghi al thread che posside gli oggetti l'onere di leggerli/scriverli (usando signals e slots per comunicare tra i threads.

    P.S.

    • le variabili globali sono nel 99% dei casi errori di design.
    • goto urta la vista di qualsiasi programmatore in quanto e' un incubo da debuggare. evitalo come la peste. nel tuo caso puoi usare do{}while();


  • Accetto tutto, anche il do while al posto del loop che ho utilizzato. Ma sono convinto che i goto, se bene utilizzati, risolvono molti casi di complicazione di condici. Non ne saprei fare a meno.



  • Ho speso troppo tempo a debuggare codice con goto da odiarlo con tutto me stesso.

    Se mi capita di vedere un goto nella code review per me e' uno stop istantaneo quindi capisco di esser "di-parte" ma la maggior parte delle volte se goto semplifica il codice significa che inserendo una funzione separata si semplifica ancora di piu'



  • Allora eviterò di usare i goto almeno nelle routines che posto, per evitare di perdere la tua preziosa consulenza.
    Per mia cultura... come dovrei modificare il Modulo_Loop_Eterno per fare in modo che scriva sul form gestito da MainWindow ? In fondo dentro Modulo_Loop_Eterno c'è semplicemente l'istruzione ptr_mainwindow->aggiungi_contatore(); : quindi l'append nel form è fatto dallo stesso modulo che lo ha creato. Non va bene così ?



  • @bvox123 said in Qthread: esempio funzionante:

    per evitare di perdere la tua preziosa consulenza

    In realta' era un pianto lamentativo generico. se i tuoi manager/clienti accettano goto chi sono io per fermarti?!

    Non va bene così ?

    No, per capirlo prova a immaginare cosa succede se nel thread principale chiami delete ptr_mainwindow; (che per la cronaca succede quando chiudi la finestra se non cambi le flag di QApplication).

    come dovrei modificare il Modulo_Loop_Eterno per fare in modo che scriva sul form gestito da MainWindow ?

    definisci un Q_SIGNAL void aggiungi_contatore(); poi connetti quel segnale a un nuovo slot nella main window che si prende cura di aggiornare il contatore



  • Mettiamola così.
    Forse non sai che una volta, prima dell'avvento delle scuole medie i ragazzi, dopo la quinta elementare, frequentavano la sesta, la settima e l'ottava: non davano un titolo di studio, ma permetteva agli studenti di continuare negli studi. Bene, io sono uno di quelli, non ho un capo manager, ma sono manager di me stesso e sono anche molto esigente: me ne dico spesso di tutti i colori.
    Ora sono tornato in prima elementare, dato che le conoscenze acquisite 40 anni fa sono obsolete a vantaggio di cose strane: le classi, con signals e slot. E allora...
    nel Modulo_Loop_Eterno.h aggiungo Q_SIGNAL void aggiungi_contatore(); fra i Signals.
    poi nel modulo Modulo_Loop_Eterno.cpp sostituisco ptr_mainwindow->aggiungi_contatore(); con QObject::connect(this, SIGNAL (aggiungi_contatore()), ptr_mainwindow, SLOT (aggiungi_contatore()));
    Ho fatto così, ma la routine mainwindow::aggiungi_contatotore() non riesce ad indirizzarla, emettendo i seguenti errori al momento esecuzione:
    Contatore: 4
    QObject::connect: No such slot MainWindow::aggiungi_contatore() in ../ProvaThread/modulo_loop_eterno.cpp:34
    QObject::connect: (receiver name: 'MainWindow')
    Contatore: 5
    QObject::connect: No such slot MainWindow::aggiungi_contatore() in ../ProvaThread/modulo_loop_eterno.cpp:34
    QObject::connect: (receiver name: 'MainWindow')
    Cosa dovrei aggiungere ? (Per favore, ricordati che sono in prima elementare).



  • no, al posto di ptr_mainwindow->aggiungi_contatore(); ci va emit aggiungi_contatore();.

    il connect va dove metti tutti gli altri connect, dopo istanza_Modulo_Loop->moveToThread(istanza_QThread);

    L'idea e' che il thread secondario "parla" attraverso i signals, il thread primario li ascolta e agisce di conseguenza. il thread secondario non agisce mai, dice solo al primario cosa fare



  • Va bene, ma come fa a capire che la routine aggiungi_contatore si trova nel mainwindow ? E' solo nel mainwindow che ho inserito quei connect, quindi se questo nuovo lo devo aggiugere a quelli già presenti, non ci sarà nessun posto, all'interno di Modulo_Loop_Eterno, in cui si indirizza a mainwindow. Sbaglio ?



  • Ho messo QObject::connect(istanza_Modulo_Loop, SIGNAL (aggiungi_contatore()), this, SLOT (aggiungi_contatore())); fra gli altri connect ma in esecuzione mi dice che non riconosce la routine aggiungi_contatore così:
    QObject::connect: No such slot MainWindow::aggiungi_contatore() in ../ProvaThread/mainwindow.cpp:35
    QObject::connect: (receiver name: 'MainWindow')



  • @bvox123 said in Qthread: esempio funzionante:

    Sbaglio ?

    No, e questo e' esattamante l'obbiettivo. uno dei principi S.O.L.I.D. si focalizza sul fatto che ogni classe deve fare 1 cosa e una cosa sola. nel tuo caso Modulo_Loop_Eterno deve solo occuparsi del contatore, non e' il suo lavoro sapere cosa aggiornare nella GUI. infatti, Modulo_Loop_Eterno non dovrebbe nemmeno sapere se una gui esiste o no



  • Avevo aggiunto un altro commento. Ci deve essere un errore di sintassi, perché non riconosce la routine aggiungi_contatore.



  • Scusa me l'ero perso.

    aggiungi_contatore e' in public: non in public slots: o lo dichiari come slot o (probabilemte preferibile) usi la connect di Qt5 QObject::conect(istanza_Modulo_Loop,&Modulo_Loop_Eterno::aggiungi_contatore,this,&MainWindow::aggiungi_contatore);



  • Perfetto. GRANDE VRonin.


Log in to reply
 

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