PYSIDEはワーカースレッドのメインスレッドからのシグナルを待つ
-
14-12-2019 - |
質問
私は私のスクリプトの1つにGUIを追加することにしました。スクリプトは単純なWebスクレーパーです。私はダウンロードおよびデータの解析としてワーカースレッドを使用することにしました。私はPysideを使うことにしましたが、一般的なQTについての私の知る知識はかなり限られています。
スクリプトがCAPTCHAを介してユーザ入力を待つことになっているため、QLineEdit
がreturnPressed
が発生し、それをワーカースレッドに送信するまで待機し、それを検証用に送信できるようにすることにしました。それは戻りキーが押されるのを待っているよりも優れているはずです。
信号を待っているように思われるように思われるのは、この。ワーカースレッド内のスレッドとローカルイベントループを越えてシグナリングしても、私の解決策をもう少し複雑にします。
それを数時間ゆっくりとした後はまだうまくいきません。
起こることになっているもの:
- CAPTCHAを参照するまでデータをダウンロードしてループ を入力します。
- CAPTCHAをダウンロードしてユーザーに表示するには、
QEventLoop
を呼び出して -
QEventLoop
クラス の - 検証が失敗した場合は、CAPTCHAとループを検証し、それ以外の場合は最後にダウンロード可能なURLを再試行してから、次のURL に進みます。
self.loop.exec_()
を起動します。
loop.quit()
で接続されているワーカースレッドスロット内のself.line_edit.returnPressed.connect(self.worker.stop_waiting)
を呼び出すことで、main_window
を終了します。
起こったこと:
-
...上記を参照してください...
-
QEventLoop
の終了は機能しません。self.loop.isRunning()
は、False
を呼び出した後にexit()
を返します。self.isRunning
はTrue
を返します。そのようなスレッドは奇妙な状況下で死んでいないようにします。それでも、ねじはself.loop.exec_()
ラインで停止します。このようにスレッドは、イベントループが実行されていなくてもイベントループを実行しています。 -
GUIは、ワーカースレッドクラスのスロットとして応答します。テキスト蜂がワーカースレッドに送信され、イベントループのステータス、およびスレッド自体のステータスを確認できますが、上記の行が実行された後は何も実行されません。
コードはビットであるので、importantを除外する疑似コードPython-Mixのビットを追加します。
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_()
. 解決
スロットは、QThread
を作成したスレッド内で実行され、QThread
がコントロールするスレッドではありません。
QObject
をスレッドに移動し、そのスロットを信号に接続し、スレッド内でスレッドを実行する必要があります。
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()
. 他のヒント
あなたが記述しているもの QWaitCondition
。
簡単な例:
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_())
.