Developpez.com - Rubrique Qt

Le Club des Développeurs et IT Pro

Retour sur une nouveauté de Qt 5.10 pour la programmation concurrente :

QThread::create() pour créer un fil d'exécution depuis une fonction anonyme

Le 2018-04-20 00:42:28, par dourouc05, Responsable Qt & Livres
QThread est une ancienne classe de Qt : elle date de la version 2.2, sortie en septembre 2000. Elle sert à lancer du code dans un fil d’exécution séparé, par exemple pour éviter de geler l’interface graphique quand un code plus lourd est lancé. Depuis un certain temps, on dispose de deux manières d’utiliser QThread : soit créer une classe dérivée dans le seul objectif de surcharger la méthode run(), soit créer une classe ouvrière connectée aux signaux de QThread.

Cependant, Qt 5.10 apporte une nouvelle manière de créer un fil d’exécution avec QThread, en profitant des derniers ajouts de C++11 : QThread::create() prend en argument une fonction anonyme qui sera lancée dans le fil d’exécution. Cette API ressemble très fortement à la bibliothèque standard, en particulier la classe bien nommée std::thread. L’un des avantages principaux de cette manière de procéder est qu’elle évite de générer une sous-classe de QThread uniquement pour en réimplémenter une méthode. Contrairement à std::thread (mais de manière similaire aux autres utilisations de QThread), il faudra lancer le fil d’exécution manuellement par la suite. Cela laisse la possibilité de définir une priorité, de connecter des signaux, etc., avant l’exécution.

Code :
1
2
3
4
5
6
QThread *thread = QThread::create(myFunction);
 
thread->setObjectName("WorkerThread"); // name to appear in ps, task manager, etc.
connect(thread, &QThread::started, gui, &Gui::threadHasStarted);
 
thread->start();
Avec un compilateur C++17, il est aussi possible de passer plus d’un argument à QThread::create() : ils seront automatiquement passés à la fonction dans le fil d’exécution, tout comme avec std::thread.

Si cette fonction n’est implémentée que depuis Qt 5.10, ce n’est pas parce qu’elle est particulièrement dure à implémenter, plutôt grâce à un changement de politique au niveau de Qt. En effet, avant Qt 5, le code de Qt ne pouvait pas obliger à disposer de la STL (pour des raisons historiques). Depuis Qt 5.0, ce choix a été revu : il existe bien plus d’implémentations raisonnables de la STL. Ainsi, elle peut être utilisée pour implémenter des fonctionnalités de Qt, mais sans être visible à l’utilisateur. L’objectif est d’obtenir des binaires indépendants de la bibliothèque standard utilisée par Qt, de telle sorte qu’aucune contrainte n’est imposée sur les applications Qt — avec l’inconvénient que Qt doit recoder une partie de la STL.

Après moult débats, la décision a été prise pour Qt 5.10 d’abandonner cette politique : Qt peut maintenant exposer des types de la STL. Cela devrait permettre d’en simplifier largement le code (dès que les changements d’API seront autorisés, c’est-à-dire pour Qt 6 au plus tôt). L’inconvénient est que les utilisateurs ne pourront plus mélanger les implémentations de la STL. Par contre, cela a permis d’implémenter la méthode QThread::create sans devoir proposer d’implémentation de std::future dans Qt.

Source : New in Qt 5.10: QThread::create.
  Discussion forum
6 commentaires
  • ymoreau
    Membre émérite
    Envoyé par dourouc05
    Contrairement à std::thread (mais de manière similaire aux autres utilisations de QThread), il faudra lancer le fil d’exécution manuellement par la suite. Cela laisse la possibilité de définir une priorité, de connecter des signaux, etc., avant l’exécution.
    C'est la seule différence avec QtConcurrent::run si je ne me trompe pas ?
    Parce que j'allais dire que pour exécuter une lambda dans un thread il y avait déjà plus simple que ce nouveau QThread::create
  • Matthieu76
    Membre éclairé
    Mais ... ? Euh ... Je comprends pas trop l’intérêt du truc.
    C'est pas déjà ce que fait un singleShot ?

    Moi je fais un truc du genre pour faire de l'Asynchone :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int resultOfSum;
    
    void MySumAsync(int x, int y)
    {
        QTimer::singleShot(0, this, SLOT( [] (int x, int y) {MySum(x, y);}));
    }
    
    void MySum(int x, int y)
    {
        resultOfSum = x + y;
    }
    En gros dans mon code j'ai une classe Controller et à chaque appuie de bouton je démarre un singleShot et dedans j'appelle la fonction de mon controller associé. Es-ce une bonne méthode de faire cela ? Et vous, comment faîtes-vous ?

    PS : Dans l'exemple je ne comprends pas cette variable &Gui::threadHasStarted
  • mattf06
    Membre à l'essai
    Envoyé par Matthieu76
    Mais ... ? Euh ... Je comprends pas trop l’intérêt du truc.
    C'est pas déjà ce que fait un singleShot ?
    L'utilisation de QTimer::singleShot te permet juste de retarder l'exécution de ta méthode. Si cela est en réponse à une action UI, donc dans le thread UI, ta méthode sera mise dans la queue d'événement UI et traitée ultérieurement. Si ta méthode fait un traitement long (plusieurs secondes par exemple) cela bloquera ta UI, d'où l'utilité des thread.
  • Matthieu76
    Membre éclairé
    Un singleShot ne place pas la méthode à exécuter dans un thread ?
  • ymoreau
    Membre émérite
    Non. Sauf si l'objet récepteur est appartient à un autre thread, la règle classique signal/slot s'applique de la même façon avec le singleShot qu'avec n'importe quelle autre connexion.
  • Matthieu76
    Membre éclairé
    Du coup, faut que j'utilise QtConcurrent::run pour run mes méthodes lors de l'appuis d'un bouton ? Es-ce le même fonctionnement qu'une Task en C# ? Merci d'avance pour votre aide