Article original publié chez Makina Corpus
Il y a de nombreux billets de blogs, posts sur des forums, tutoriaux, pages Wiki et autres, mais au final, à part le fameux "You're doing it wrong", qui peut paraître obscure au premier abord, je n'ai pas trouvé de résumé de l'attrape-nigaud que je vais illustrer ici.
Le piège
Naturellement, quand on veut faire une thread, on a envie d'hériter de l'objet QThread. C'est ce qu'on fait avec le module threading de python (en Java aussi il me semble).
Voici ce qu'on écrit naturellement : Objet, la classe qui file l'ordre et Worker, une classe qui bosse dur en arrière plan. On connecte les signaux et on démarre !
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import QApplication
class Object(QObject):
def emitSignal(self):
self.emit(SIGNAL("aSignal()"))
class Worker(QThread):
def aSlot(self):
self.thread().sleep(1)
print "Slot is executed in thread : ", self.thread().currentThreadId()
if __name__ == "__main__":
app = QApplication(sys.argv)
print "Main application thread is : ", app.thread().currentThreadId()
worker = Worker()
obj = Object()
QObject.connect(obj, SIGNAL("aSignal()"), worker.aSlot)
worker.start()
obj.emitSignal()
print "Done."
app.exec_()
Ici, comme le slot aSlot() est défini dans la classe Worker, qui hérite de QThread, on pense naturellement qu'il va être exécuté en arrière-plan. Que nenni!
Main application thread is : 140068661352224
# (... wait 1 sec ...)
Slot is executed in thread : 140068661352224
Done.
La solution
Un secret ? Les QThread ne sont pas des threads. Elles enrobent l'execution d'une thread.
L'appartenance (affinité) d'un objet à une thread détermine le type de connexion utilisé par défaut, et par conséquent le comportement lors de l'execution des slots.
Ce qu'il faut écrire : Worker n'est plus une QThread, on force son affinité dans une thread avec moveToThread().
class Object(QObject):
def emitSignal(self):
self.emit(SIGNAL("aSignal()"))
class Worker(QObject):
def aSlot(self):
self.thread().sleep(1)
print "Slot is executed in thread : ", self.thread().currentThreadId()
if __name__ == "__main__":
app = QApplication(sys.argv)
print "Main application thread is : ", app.thread().currentThreadId()
worker = Worker()
obj = Object()
thread = QThread()
worker.moveToThread(thread)
QObject.connect(obj, SIGNAL("aSignal()"), worker.aSlot)
thread.start()
obj.emitSignal()
print "Done."
app.exec_()
Désormais, l'exécution est bien asynchrone, comme on le souhaitait.
Main application thread is : 139961882056480
Done.
# (... wait 1 sec ...)
Slot is executed in thread : 139961512900352
Tout simplement ! Si j'avais lu mon article avant, je n'aurais pas perdu autant de temps à lire toutes ces docs ambiguës sur le Net.
Sources:
- Explications complètes
- Thread Basics (attention au piège)
#qt, #python, #tips - Posted in the Dev category