===== Les threads et les QObject ===== [[QThread]] hérite de [[QObject]]. Il émet des signaux pour indiquer que le thread a commencé ou a terminé son exécution, et fournit également quelques slots. ==== Introduction ==== Plus intéressant encore, les [[QObject]] peuvent être utilisés dans de multiples threads, émettre des signaux appelant des slots dans d'autres threads, et poster des évènements aux objets qui "vivent" dans d'autres threads. Cela est possible car chaque thread est autorisé à posséder sa propre boucle d'évènements. Sujets : * [[#La ré-entrance de QObject]] * [[#Boucle d'évènements par thread]] * [[#Accès à des sous-classes de QObject depuis d'autres threads]] * [[#Les signaux et les slots à travers les threads]] ==== La ré-entrance de QObject ==== [[QObject]] est ré-entrant((Une fonction ou un objet est dit ré-entrant quand il peut être utilisé simultanément par plusieurs threads, en considérant son implémentation et son interface externe.)). La plupart de ses sous-classes non GUI, telles que [[QTimer]], [[QTcpSocket]], [[QUdpSocket]], [[QFtp]] et [[QProcess]], sont aussi ré-entrantes, ce qui permet de les utiliser simultanément depuis plusieurs threads. Notez que ces classes sont conçues pour être générées et utilisées au sein d'un seul thread; le fonctionnement n'est pas garanti si l'on crée un objet dans un thread et que l'on appelle ses fonctions depuis un autre thread. Il y a trois contraintes à considérer : * //L'enfant d'un [[QObject]] doit toujours être créé dans le thread où son parent a été créé.// Cela implique, entre autres, que vous ne devriez jamais passer l'objet [[QThread]] (''this'') comme le parent d'un objet créé dans le thread (puisque que l'objet [[QThread]] a lui-même été créé dans un autre thread). * //Les objets avec évènements ne peuvent être utilisés que dans un thread unique.// En particulier, cela s'applique aux [[timers|mécanismes de timers]] et au [[qtnetwork|module de réseau]]. Par exemple, il est impossible de démarrer un timer ou de connecter une socket dans un thread qui n'est pas le [[qobject#thread|thread de l'objet]]. * //Il faut s'assurer que tous les objets qui ont été créés dans un thread sont bien supprimés avant de supprimer le [[QThread]].// La façon la plus simple de le faire est de créer les objets sur la pile dans l'implémentation de votre [[qthread#run|run()]]. Bien que [[QObject]] soit ré-entrant, les classes de GUI comme [[QWidget]] et toutes ses sous-classes ne sont pas ré-entrantes. Elles ne peuvent être utilisées qu'à partir du thread principal. Comme précisé précédemment, [[qcoreapplication#exec|QCoreApplication::exec()]] doit aussi être appelée à partir de ce thread. Il est donc impossible d'utiliser les classes de GUI dans d'autres threads que le thread principal. En pratique, cette impossibilité peut être contournée en plaçant les opérations coûteuses en temps de calcul dans un thread de travail séparé. Puis, à la clôture de ce thread, il suffit d'afficher les résultats à l'écran dans le thread principal. C'est l'approche utilisée pour implémenter les exemples [[threads-mandelbrot|Mandelbrot]] et [[network-blockingfortuneclient|Blocking Fortune Client]]. ==== Boucle d'évènements par thread ==== Chaque thread peut posséder sa propre boucle évènementielle. Le thread initial démarre sa boucle évènementielle en utilisant [[qcoreapplication#exec|QCoreApplication::exec()]]; les autres threads peuvent démarrer une boucle évènementielle en utilisant [[qthread#exec|QThread::exec()]]. Comme [[QCoreApplication]], [[QThread]] fournit une fonction [[qthread#exit|exit(int)]] et un slot [[qthread#quit|quit()]]. Dans un thread, la présence d'une boucle évènementielle est indispensable pour utiliser certaines classes non-GUI (comme [[QTimer]], [[QTcpSocket]], et [[QProcess]]). Cela permet également de connecter des signaux à partir de n'importe quel thread aux slots d'un thread spécifique. Cela est expliqué plus en détail dans la section [[#Les signaux et les slots à travers les threads]] ci-dessous. {{ http://qt.developpez.com/doc/4.6/images/threadsandobjects.png |}} On dit qu'une instance de [[QObject]] "vit" dans le thread dans lequel elle a été créée. Les évènements à destination de cet objet sont envoyés par la boucle évènementielle de ce thread. Le thread dans lequel vit un objet est disponible via [[qobject#thread|QObject::thread()]]. Notez que [[qobject#thread|QObject::thread()]] retourne zéro pour les QObjects créés avant [[QApplication]]. Cela signifie pour ces objets que le thread principal ne va gérer que les évènements postés; la propagation d'autres évènements n'est pas du tout effectuée pour les objets dépourvus de thread. Utilisez la fonction [[qobject#moveToThread|QObject::moveToThread()]] pour changer les liens d'un thread avec un objet et ses enfants (un objet ne peut pas être déplacé s'il a un parent). Il n’est pas sûr d’appeler ''delete'' sur un [[QObject]] depuis un autre thread que le possesseur de l'objet (ou même d'accéder à l'objet par d'autres méthodes), à moins de pouvoir garantir que l'objet n'est pas en train de propager des évènements à cet instant précis. A la place, utilisez plutôt [[qobject#deleteLater|QObject::deleteLater()]]: un évènement [[qevent#Type-enum|DeferredDelete]] sera ainsi posté puis traité par la boucle évènementielle du thread quand elle aura la main. Par défaut, le thread possesseur d'un [[QObject]] est celui qui l'a créé. Mais ce n'est plus vrai si [[qobject#moveToThread|QObject::moveToThread()]] a été appelé après la création de l'objet. Si aucune boucle évènementielle n'est lancée, les évènements ne seront pas distribués à l'objet. Par exemple, si vous créez un objet [[QTimer]] dans un thread mais que vous n'appelez jamais [[qthread#exec|exec()]], le [[QTimer]] n'émettra jamais son signal [[qtimer#timeout|timeout()]]. L'appel de [[qobject#deleteLater|deleteLater()]] ne fonctionnera pas non plus. (Ces restrictions s'appliquent également au thread principal.) Il est possible de poster manuellement des évènements dans n'importe quel thread, et à n'importe quel moment, en utilisant la fonction thread-safe [[qcoreapplication#postEvent|QCoreApplication::postEvent()]]. Les évènements seront automatiquement envoyés par la boucle évènementielle du thread dans lequel l'objet a été créé. Les filtres d'évènements sont utilisables dans tous les threads, à condition que l'objet de contrôle "vive" dans le même thread que l'objet contrôlé. De la même façon, [[qcoreapplication#sendEvent|QCoreApplication::sendEvent()]] est utilisable uniquement pour envoyer des évènements aux objets vivant dans le thread où la fonction est appelée. Cette restriction n'existe pas pour la fonction [[qcoreapplication#postEvent|postEvent()]]). ==== Accès à des sous-classes de QObject depuis d'autres threads ==== [[QObject]] et toutes ses sous-classes ne sont pas thread-safe. Cela inclut la totalité du système d'envoi des évènements. Il est important de garder à l'esprit que la boucle évènementielle peut envoyer des évènements à votre sous-classe de [[QObject]] alors que vous êtes en train d'accéder à l'objet à partir d'un autre thread. Si vous appelez une fonction d'une sous-classe de [[QObject]] qui ne vit pas dans le thread actuel et qu'il est possible que l'objet reçoive des évènements, alors vous devez protéger avec un mutex tous les accès aux données internes de votre sous-classe de [[QObject]]; dans le cas contraire, des crashs ou des comportements indésirables peuvent survenir. Comme d'autres objets, les [[QThread]] vivent dans le thread dans lequel l'objet a été créé -- et non dans le thread qui est créé quand [[qthread#run|QThread::run()]] est appelé. Il n'est en général pas sûr de créer des slots dans votre sous-classe de [[QThread]], à moins de protéger les variables membres avec un mutex. D'un autre côté, vous pouvez émettre de façon sûre des signaux à partir d'une implémentation de [[qthread#run|QThread::run()]] car l'émission de signaux est thread-safe((Une fonction ou un objet est dit thread-safe quand il peut être utilisé simultanément par plusieurs threads, en considérant uniquement son implémentation.)). ==== Les signaux et les slots à travers les threads ==== Qt supporte les types de connexions "signal-slot" suivants : * [[qt#ConnectionType-enum|Auto Connection]] (défaut) Le comportement est identique à //Direct Connection// si l'émetteur et le récepteur sont dans le même thread. Le comportement est identique à //Queued Connection// si l'émetteur et le récepteur sont dans des threads différents. * [[qt#ConnectionType-enum|Direct Connection]] Le slot est invoqué immédiatement quand le signal est émis. Le slot est exécuté dans le thread de l'émetteur, qui n'est pas nécessairement le thread du récepteur. * [[qt#ConnectionType-enum|Queued Connection]] Le slot est invoqué quand le contrôle retourne à la boucle évènementielle du thread du récepteur. Le slot est exécuté dans le thread du récepteur. * [[qt#ConnectionType-enum|Blocking Queued Connection]] Le slot est invoqué comme pour la //Queued Connection//, excepté que le thread courant est bloqué jusqu'à ce que le slot se termine. **Note :** Utiliser ce type pour connecter des objets dans le même thread causera un verrou mortel (deadlock). * [[qt#ConnectionType-enum|Unique Connection]] Le comportement est le même que pour //Auto Connection// mais la connexion est faite seulement si cela ne duplique pas une connexion existante. C'est-à-dire, si le même signal est déjà connecté au même slot pour la même paire d'objets, alors la connexion n'est pas effectuée et [[qobject#connect|connect()]] retourne ''false''. Le type de connexion peut être spécifié en passant un argument supplémentaire à [[qobject#connect|connect()]]. Soyez attentifs au fait qu'utiliser des connexions directes quand l'émetteur et le récepteur vivent dans des threads différents n'est pas sûr si une boucle évènementielle est lancée dans le thread du récepteur. Cela n'est pas sûr pour la même raison qu'il n'est pas sûr d'appeler une fonction d'un objet vivant dans un autre thread. [[qobject#connect|QObject::connect()]] lui-même est thread-safe. L'exemple [[threads-mandelbrot|Mandelbrot]] utilise une //Queued Connection// pour communiquer entre un thread de travail et le thread principal. Pour éviter de bloquer la boucle évènementielle du thread principal (et, par conséquent, l'interface utilisateur de l'application), tous les calculs des fractales de Mandelbrot sont effectués dans un thread de travail séparé. Le thread émet un signal lorsqu'il a terminé le rendu de la fractale. De même, l'exemple [[network-blockingfortuneclient|Blocking Fortune Client]] utilise un thread séparé pour communiquer de façon asynchrone avec un serveur TCP. ==== Remerciements ==== Merci à pour la traduction et à ainsi qu'à pour leur relecture !