PySide attendre le signal d'thread principal dans un thread de travail
-
14-12-2019 - |
Question
J'ai décidé d'ajouter une interface à l'un de mes scripts.Le script est un simple web grattoir.J'ai décidé d'utiliser un thread de travail, le téléchargement et l'analyse des données peut prendre un certain temps.J'ai décidé d'utiliser PySide, mais ma connaissance de l'intervalle Qt en général est assez limité.
Comme le script est censé attendre une entrée de l'utilisateur au moment de l'entrée à travers un captcha, j'ai décidé qu'il devrait attendre jusqu'à ce qu'un QLineEdit
les feux de returnPressed
et puis de l'envoyer du contenu à l'thread de travail de sorte qu'il peut l'envoyer pour validation.Qui devrait être mieux que occupé-en attendant le retour touche à presser.
Il semble que l'attente d'un signal n'est pas aussi simple que je le pensais et après avoir cherché pendant un moment je suis tombé sur plusieurs solutions similaires à cette.La signalisation dans les threads et un événement local en boucle dans le thread de faire ma solution un peu plus compliqué cependant.
Après bricoler avec elle pendant plusieurs heures, cela ne fonctionne toujours pas.
Ce qui est censé se produire:
- Téléchargement de données jusqu'à ce que appelé captcha et entrer dans une boucle
- Télécharger le captcha et l'afficher à l'utilisateur, démarrage
QEventLoop
en appelantself.loop.exec_()
- Sortie
QEventLoop
en appelantloop.quit()
dans un worker threads fente qui est connecté viaself.line_edit.returnPressed.connect(self.worker.stop_waiting)
dans lemain_window
classe - Valider le captcha et boucle si la validation échoue, sinon réessayer la dernière url, ce qui devrait être téléchargeable maintenant, pour passer ensuite à l'url suivante
Ce qui se passe:
...voir ci-dessus...
La sortie
QEventLoop
ne fonctionne pas.self.loop.isRunning()
retourneFalse
après l'appel de sonexit()
.self.isRunning
retourneTrue
, ce fil ne semble pas mourir sous d'étranges circonstances.Toujours le thread s'arrête à laself.loop.exec_()
ligne.En tant que tel, le thread est bloqué l'exécution de la boucle d'événement, même si la boucle d'événements me dit qu'elle ne fonctionne pas plus.Le GUI répond que les fentes du travailleur de la classe thread.Je peux voir le texte étant de l'envoyer à la thread de travail, le statut de la boucle d'événement et le fil lui-même, mais rien, après l'alinéa ci-dessus est exécuté.
Le code est un peu compliqué, j'ai ajouter un peu de pseudo-code python mélange en laissant de côté le peu d'importance:
class MainWindow(...):
# couldn't find a way to send the text with the returnPressed signal, so I
# added a helper signal, seems to work though. Doesn't work in the
# constructor, might be a PySide bug?
helper_signal = PySide.QtCore.Signal(str)
def __init__(self):
# ...setup...
self.worker = WorkerThread()
self.line_edit.returnPressed.connect(self.helper_slot)
self.helper_signal.connect(self.worker.stop_waiting)
@PySide.QtCore.Slot()
def helper_slot(self):
self.helper_signal.emit(self.line_edit.text())
class WorkerThread(PySide.QtCore.QThread):
wait_for_input = PySide.QtCore.QEventLoop()
def run(self):
# ...download stuff...
for url in list_of_stuff:
self.results.append(get(url))
@PySide.QtCore.Slot(str)
def stop_waiting(self, text):
self.solution = text
# this definitely gets executed upon pressing return
self.wait_for_input.exit()
# a wrapper for requests.get to handle captcha
def get(self, *args, **kwargs):
result = requests.get(*args, **kwargs)
while result.history: # redirect means captcha
# ...parse and extract captcha...
# ...display captcha to user via not shown signals to main thread...
# wait until stop_waiting stops this event loop and as such the user
# has entered something as a solution
self.wait_for_input.exec_()
# ...this part never get's executed, unless I remove the event
# loop...
post = { # ...whatever data necessary plus solution... }
# send the solution
result = requests.post('http://foo.foo/captcha_url'), data=post)
# no captcha was there, return result
return result
frame = MainWindow()
frame.show()
frame.worker.start()
app.exec_()
La solution
Le logement est exécutée dans le thread qui a créé le QThread
, et pas dans le thread qui l' QThread
les contrôles.
Vous avez besoin de déplacer un QObject
au fil et de vous connecter sa fente pour le signal, et que le logement sera exécutée dans le thread:
class SignalReceiver(QtCore.QObject):
def __init__(self):
self.eventLoop = QEventLoop(self)
@PySide.QtCore.Slot(str)
def stop_waiting(self, text):
self.text = text
eventLoop.exit()
def wait_for_input(self):
eventLoop.exec()
return self.text
class MainWindow(...):
...
def __init__(self):
...
self.helper_signal.connect(self.worker.signalReceiver.stop_waiting)
class WorkerThread(PySide.QtCore.QThread):
def __init__(self):
self.signalReceiver = SignalReceiver()
# After the following call the slots will be executed in the thread
self.signalReceiver.moveToThread(self)
def get(self, *args, **kwargs):
result = requests.get(*args, **kwargs)
while result.history:
...
self.result = self.signalReceiver.wait_for_input()
Autres conseils
Ce que vous décrivez est idéal pour QWaitCondition
.
Exemple Simple:
import sys
from PySide import QtCore, QtGui
waitCondition = QtCore.QWaitCondition()
mutex = QtCore.QMutex()
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__()
self.text = QtGui.QLineEdit()
self.text.returnPressed.connect(self.wakeup)
self.worker = Worker(self)
self.worker.start()
self.setCentralWidget(self.text)
def wakeup(self):
waitCondition.wakeAll()
class Worker(QtCore.QThread):
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
def run(self):
print "initial stuff"
mutex.lock()
waitCondition.wait(mutex)
mutex.unlock()
print "after returnPressed"
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
m = Main()
m.show()
sys.exit(app.exec_())