2017-09-02 4 views
1

J'écris une application de démonstration pour faciliter ma courbe d'apprentissage QT. Mon but est de mettre à jour les valeurs d'un thread qui s'exécute en arrière-plan en tant que générateur de données. J'ai écrit le QML et lié les membres C++ en utilisant l'approche de liaison de données standard QT, c'est-à-dire Q_Property. Actuellement, la solution fonctionne comme prévu mais a voulu confirmer si c'est la bonne façon d'implémenter la même chose.Est-ce la bonne façon de mettre à jour QML à partir du thread en utilisant des signaux multi-couche?

Idea

  1. Générer des données dans un filetage (classe DemoData)
  2. signaux émettant de notifier une autre classe (classe VitalData)
  3. signaux Emitting Q_PROPERTY (de classe VitalData) pour mettre à jour l'interface utilisateur

Recherche

  1. Dois-je générer des données et informer l'interface utilisateur des modifications apportées à une seule classe et envoyer cette instance de classe à un nouveau thread? Comme je peux utiliser un seul signal pour mettre à jour l'interface utilisateur dans ce cas.
  2. Selon la conception actuelle, est-ce que les performances seront médiocres ou, dans le pire des cas, certaines données peuvent être manquées à l'interface utilisateur en raison d'une fente de signal rapide?

Mon but est de garder la classe du générateur de données découplée.

Enfin, le code

//A data generator class - this can be altered by some other class if neccessary 
class DemoData : public QObject 
{ 
    Q_OBJECT 
    int nextUpdateIndex = 0; 

public slots: 
    void generateData() 
    { 
     int hrValIndex = 0, spo2ValIndex = 0, respValIndex = 0, co2ValIndex = 0; 

     while(true) { 
      switch(nextUpdateIndex) { 
      case 0: 
       emit valueUpdated(nextUpdateIndex, demoHRRates[hrValIndex]); 
       if(hrValIndex == ((sizeof demoHRRates)/(sizeof(int))) - 1) 
        hrValIndex = 0; 
       else 
        hrValIndex++; 
       nextUpdateIndex = 1; 
       break; 
      } 
      QThread::sleep(1); 
     } 
    } 
signals: 
    //Signal to notify the UI about new value 
    void valueUpdated(int index, int data); 
}; 


//Class to interact with QML UI layer. This class only hold properties and it's binding 
class VitalData : public QObject 
{ 
    Q_OBJECT 
    Q_PROPERTY(int hrRate READ getHrRate NOTIFY hrRateChanged) 

    public: 
    int getHrRate() const { 
     return m_hrRate; 
    } 

public slots: 
    void getData(int index, int value) 
    { 
     switch(index){ 
     case 0: 
      m_hrRate = value; 
      emit hrRateChanged(); 
      break; 
     } 
    } 

signals: 
    //This signal actually notifies QML to update it value 
    void hrRateChanged(); 
}; 

int main() 
{ 
    QGuiApplication app(argc, argv); 

    //Data generator class is getting linked with UI data feeder class 
    VitalData med; 
    DemoData demo; 
    QObject::connect(&demo, SIGNAL(valueUpdated(int, int)), &med, SLOT(getData(int, int))); 

    //Standard way to launch QML view 
    QQuickView view; 
    view.rootContext()->setContextProperty("med", &med); 
    view.setSource(QUrl(QStringLiteral("qrc:/main.qml"))); 
    view.show(); 

    //Moving data generator to a background thread 
    QThread thread; 
    demo.moveToThread(&thread); 
    QObject::connect(&thread, SIGNAL(started()), &demo, SLOT(generateData())); 
    thread.start(); 

    return app.exec(); 
} 

Nouveau code pour la sortie de fil

int main() 
{ 
    QThread thread; 
    demo.moveToThread(&thread); 
    QObject::connect(&thread, SIGNAL(started()), &demo, SLOT(generateData())); 
    QObject::connect(qApp, &QCoreApplication::aboutToQuit, &thread, [&thread](){ 
     thread.requestInterruption(); 
     thread.wait(); 
    }); 
    thread.start(); 
} 


class DemoData : public QObject 
{ 
    Q_OBJECT 
public slots: 
    void generateData() 
    { 
     while(!QThread::currentThread()->isInterruptionRequested()) { 
      switch(nextUpdateIndex) { 
       case 0: 
       break; 
      } 
      QThread::msleep(200); 
      qDebug() << "Thread running.."; 
     } 

     //This quit was necessary. Otherwise even with requestInterruption call thread was not closing though the above debug log stopped 
     QThread::currentThread()->quit(); 
    } 
}; 

Répondre

3

En ce qui concerne la conception générale:

me semble bon. Personnellement, je cours toujours moveToThread avant tout le reste, mais cela ne devrait pas influencer le résultat dans ce cas. (La seule chose qui prête à confusion, c'est que vous avez nommé la méthode getData .C'est un setter pas un getter et doit être nommé en conséquence)

Cependant, la génération de vos données est possible, mais pas optimale. Avec le QThread::sleep(1) vous bloquez le eventloop, ce qui rend impossible l'arrêt du thread avec élégance. Au lieu de cela, vous devriez utiliser une minuterie. La minuterie et la classe DemoData seront toujours en cours d'exécution sur ce thread, mais en utilisant timer et eventloop. De cette façon, le QThread peut recevoir des événements, etc. (Par exemple, si vous avez besoin d'envoyer des données à votre classe plus tard, vous pouvez utiliser une fente, mais seulement, si le eventloop du fil peut fonctionner):

class DemoData : public QObject 
{ 
    Q_OBJECT 
    int nextUpdateIndex = 0; 

public slots: 
    void generateData() 
    { 
     auto timer = new QTimer(this); 
     connect(timer, &QTimer::timeout, this, &DemoData::generate); 
     timer->start(1000); 
    } 

private slots: 
    void generate() 
    { 
     //code to generate data here, without the loop 
     //as this method gets called every second by the timer 
    } 
}; 

Il y a un autre moyen, si vous ne voulez pas utiliser les minuteurs. Vous devez réimplémenter QThread et effectuer le traitement de l'événement vous-même, mais vous ne devriez le faire que lorsqu'il n'y a pas d'autre choix. Vous devez remplacer QThread::run.

Quitter un thread avec élégance est plutôt facile, mais dépend de la façon dont votre thread est construit. Si vous avez un événement eventloop en cours, c'est-à-dire qu'il n'y a pas d'opération de blocage longue, vous pouvez simplement appeler QThread::quit et QThread::wait.Cela ne fonctionne cependant qu'avec un QThread où le eventloop est en cours d'exécution (nécessitant donc une minuterie).

QObject::connect(qApp, &QCoreApplication::aboutToQuit, &thread, [&thread](){ 
    thread.quit(); 
    thread.wait(5000); 
}); 

Si votre thread n'exécute pas le eventloop correctement, vous pouvez utiliser les demandes d'interruption. Au lieu de quitter, appelez le QThread::requestInterruption. Dans votre méthode generateData, vous devez alors utiliser des intervalles courts et vérifiez QThread::isInterruptionRequested chaque fois:

void generateData() 
{ 
    int hrValIndex = 0, spo2ValIndex = 0, respValIndex = 0, co2ValIndex = 0; 

    while(!QThread::currentThread()->isInterruptionRequested()) { 
     // code... 
     QThread::sleep(1); 
    } 
} 
+0

Eh bien d'abord je suis allé pour la minuterie et décalée à enfiler pour en savoir plus sur les threads QT. Je viens d'un arrière-plan Win32/MFC/C#, essayant ainsi toutes les choses possibles dans QT. Oui 'getData' devrait être changé en un nom associé à setter, corrigera cela. Enfin une question rapide. Quelle est la bonne façon de fermer le fil gracieusement à la sortie de l'application? Je peux diviser le sommeil d'une seconde en milliseconde plus courte avec un contrôle de rupture. J'essayais de connecter le slot 'aboutToQuit()' mais cela ne m'a pas aidé. – Anup

+1

Même QThreads possède sa propre boucle d'événements. Je vais mettre à jour la réponse en conséquence – Felix

+0

Si je vous ai bien compris, je dois mettre 'while (! QThread :: currentThread() -> isInterruptionRequested())' pour intercepter l'interruption et déclencher l'interruption sur le signal 'aboutToQuit'. c'est-à-dire 'QObject :: connect (qApp, & QCoreApplication :: aboutToQuit, & thread, [& thread]() { thread.requestInterruption(); thread.wait (1000); });' – Anup