Vous vous y prenez mal...

| Bradley T. Hughes | traducteur : Jonathan Courtois | Qt Labs |

Vous travaillez régulièrement avec QThread ? Vous utilisez moveToThread() sans vraiment savoir ce que fait cette fonction ? Vous pensez savoir ce que fait cette fonction mais vous voulez vérifier que vous ne vous trompez pas ? Cet article répondra à toutes ces questions !

Cet article est une traduction autorisée de You're doing it wrong..., par Bradley T. Hughes.

N'hésitez pas à commenter cet article !
Commentez

- Titre : Vous vous y prenez mal...

- Auteurs : Bradley T. Hughes, traducteur : Jonathan Courtois, Qt Labs

- Parution : 21 août 2009

- Mise à jour : 22 août 2010

- Licence : Copyright ® 2010 Bradley T. Hughes. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Explication

- IV. Conclusion

- V. Divers

I - L'article original

Les Qt Labs Blogs sont des blogs tenus par les développeurs de Qt, concernant les nouveautés ou les utilisations un peu extrêmes du framework.

Nokia, Qt et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction du billet You're doing it wrong..., par Bradley T. Hughes.

II - Introduction

Nous utilisons intensivement IRC pour communiquer entre nous ainsi qu'avec la communauté. Je me balade sur le canal #qt du réseau Freenode et aide les gens en répondant à leurs questions quand je le peux. Une question commune que je vois (et qui me fait grincer des dents en même temps) concerne la compréhension des threads avec Qt et comment faire en sorte que le code qu'ils ont écrit fonctionne. Les gens montrent leur code, ou des exemples basés sur leur code et je finis souvent par penser :

Vous vous y prenez mal...

Je sais que c'est difficile à dire, peut-être un peu provocant mais, en même temps, je ne peux pas m'empêcher de penser que cette hypothétique classe est une application incorrecte des principes de l'orienté objet, ainsi qu'une utilisation incorrecte de Qt.

III - Explication

class MyThread : public QThread
{
public:
MyThread()
{
moveToThread(this);
}

void run();

signals:
void progress(int);
void dataReady(QByteArray);

public slots:
void doWork();
void timeoutHandler();
};

Mon premier gros reproche avec ce code est moveToThread(this). Je vois tellement de gens utiliser cela sans même comprendre ce que cette fonction fait. Vous me le demandez ? Elle indique à Qt qu'il faut s'assurer que les gestionnaires d'événements et, par extension, les signaux et les slots, soient appelés à partir du contexte du thread spécifié. QThread est l'interface d'un thread, nous demandons ainsi au thread d'exécuter du code "à l'intérieur de lui-même". Nous faisons également cela avant que le thread soit en cours d'exécution. Même si cela semble fonctionner, ce code est source de confusion et ne correspond pas à la manière dont QThread a été conçu (toutes les fonctions de QThread ont été écrites et sont destinées à être appelées à partir du thread créant celui-ci et non depuis le thread qui vient de démarrer).

Mon impression est que moveToThread(this) apparaît dans le code des gens parce qu'ils ont vu certains blogs quelque part qui l'utilisaient. Une recherche rapide sur la toile retourne plusieurs de ces blogs qui suivent le même modèle que la classe ci-dessus, à savoir :

1. créer une classe héritant de QThread ;

2. ajouter des signaux et des slots pour faire le travail ;

3. tester le code, voir que les slots ne sont pas appelés "du bon thread" ;

4. rechercher sur Google, trouver moveToThread(this) et ajouter le commentaire "cela semble fonctionner quand j'ajoute cette fonction".

À mon avis, les problèmes ont commencé à l'étape 1. QThread a été conçu et est destiné à être utilisé comme une interface ou un point de contrôle pour un thread de système d'exploitation et non un endroit où vous pouvez mettre le code que vous voulez exécuter dans un thread. Nous, les programmeurs orientés objets, nous réalisons des sous-classes car nous voulons étendre ou spécialiser les fonctionnalités d'une classe de base. La seule raison valable de réaliser une sous-classe de QThread à laquelle je peux penser est d'ajouter des fonctionnalités que QThread n'a pas, par exemple, peut être de fournir un pointeur vers la mémoire afin d'utiliser la pile du thread, ou d'y ajouter une interface/un support du temps réel. Télécharger un fichier, interroger une base de données ou faire tout autre type de traitements ne devrait pas être ajouté dans une sous-classe de QThread. Il devrait être encapsulé dans un objet qui lui est propre.

Habituellement, cela signifie tout simplement de changer l'héritage de votre classe en QObject au lieu de QThread et éventuellement changer le nom de la classe. QThread possède un signal started() auquel vous pouvez vous connecter quand vous devez effectuer une initialisation. Pour exécuter votre code dans un nouveau thread, vous devriez instancier un QThread et assigner votre objet à ce thread en utilisant la fonction moveToThread(). Même si vous utilisez encore moveToThread() pour demander à Qt d'exécuter votre code dans un thread spécifique, vous gardez ainsi l'interface du thread bien séparée. Si nécessaire, il est maintenant possible d'avoir plusieurs instances de votre classe attribuées à un seul thread ou plusieurs instances de plusieurs classes différentes attribuées à un seul thread. En d'autres termes, il est inutile d'attacher une seule instance d'une classe à un seul thread.

IV - Conclusion

Je prends en grande partie la responsabilité de cette confusion qui accompagne l'écriture de code multithreadé avec Qt. La classe d'origine QThread était abstraite, alors la réalisation d'une sous-classe était nécessaire. C'était le cas jusqu'à ce que Qt 4.4 donne une implémentation par défaut à QThread::run(). Précédemment, la seule manière d'utiliser QThread était de réaliser une sous-classe. Avec l'ajout d'affinité des threads et la possibilité de réaliser des signaux et des slots entre objets possédant une affinité différente, tout à coup nous avons un moyen pratique de travailler avec les threads. Nous aimons les choses pratiques, nous voulons les utiliser. Malheureusement, je me suis rendu compte trop tard que forcer les gens à réaliser une sous-classe de QThread rendait les choses effectivement plus difficiles qu'il le faudrait.

Je prends également la responsabilité de ne pas avoir mis à jour les exemples et la documentation faits pour montrer aux gens comment obtenir le résultat voulu avec le minimum de maux de tête. Pour l'instant, la meilleure ressource que je peux conseiller est un sujet de blog que j'ai écrit il y a de nombreuses années.

Avertissement : tout ce que vous voyez ci-dessus est bien évidemment une opinion. J'ai beaucoup travaillé sur ces classes et ai une idée assez claire de la façon de les utiliser et comment ne pas les utiliser.

V - Divers

Merci à dourouc05 ainsi qu'à jacques_jean pour leur relecture !