Pyside Aspetta il segnale dal filo principale in un filo lavoratore
-
14-12-2019 - |
Domanda
Ho deciso di aggiungere una GUI a uno dei miei script. Lo script è un semplice raschietto web. Ho deciso di utilizzare un filo del lavoratore come scaricare e analizzare i dati può richiedere un po 'di tempo. Ho deciso di usare la Pyside, ma la mia conoscenza del QT in generale è piuttosto limitata.
Poiché lo script dovrebbe attendere che l'immissione dell'utente arrivi attraverso un CAPTCHA ho deciso che dovrebbe attendere fino a quando un QLineEdit
incendi returnPressed
e quindi inviano il suo contenuto al filo del lavoratore in modo che possa inviarlo per la convalida. Questo dovrebbe essere migliore che in attesa di essere premuto il tasto di ritorno da premere.
Sembra che in attesa di un segnale non sia dritto come pensavo che sarebbe stato e dopo aver cercato un po 'ho trovato diverse soluzioni simili a questo . Segnalazione su threads e un ciclo di eventi locali nel thread del lavoratore rendono la mia soluzione un po 'più complicata però.
Dopo aver toccato con esso per diverse ore, ancora non funzionerà.
Cosa dovrebbe accadere:
- .
- Scarica i dati fino a quando si fa riferimento a CAPTCHA e inserisci un loop
- Scarica il captcha e visualizzalo all'utente, avviare
QEventLoop
chiamandoself.loop.exec_()
- Esci dall'uscita
QEventLoop
chiamandoloop.quit()
in uno slot dei fili di lavorazione che è collegato tramiteself.line_edit.returnPressed.connect(self.worker.stop_waiting)
nella classemain_window
- Convalida Captcha e Loop Se la convalida fallisce, altrimenti riprova l'URL dell'ultimo che dovrebbe essere scaricabile ora, quindi andare avanti con l'URL successivo
Cosa succede:
- .
-
... Vedi sopra ...
-
Exhiting
QEventLoop
non funziona.self.loop.isRunning()
restituisceFalse
dopo aver chiamato il suoexit()
.self.isRunning
restituisceTrue
, in quanto tale il thread non sembrava morire in circostanze strane. Ancora il thread si ferma nella lineaself.loop.exec_()
. Come tale il filo è bloccato eseguendo il ciclo dell'evento anche se il ciclo dell'evento mi dice che non è più correre. -
La GUI risponde come fanno le slot della classe del filo lavoratore. Posso vedere il testo Apeing inviare al filo del lavoratore, lo stato del loop evento e il filo stesso, ma nulla dopo la linea sopra menzionata viene eseguita.
Il codice è un po 'contorto, in quanto tale aggiungo un po' di pseudo-code-python-mix lasciando fuori il non importante:
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_()
. Soluzione
Lo slot viene eseguito all'interno del filo che ha creato QThread
e non nella filettatura che il QThread
controlla.
È necessario spostare un QObject
sul filo e collegare il suo slot al segnale e che lo slot verrà eseguito all'interno del filo:
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()
. Altri suggerimenti
Cosa stai descrivendo Sembra ideale per QWaitCondition
.
Esempio semplice:
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_())
.