PySide waits for a signal from the main thread in the workflow

I decided to add a graphical interface to one of my scripts. script is a simple scraper. I decided to use a workflow, as loading and analyzing data may take some time. I decided to use PySide, but my Qt knowledge is generally quite limited.

Since the script should wait for user input when captcha is intercepted, I decided that it should wait for QLineEdit to start returnPressed and then send it to the workflow so that it can send it for verification.This should be better than waiting for the click to wait return keys.

It seems that waiting for the signal is not as straightforward as I thought, and that would be, and after searching for some time I came across several solutions like this . The signaling by threads and the local loop of events in the work thread make my decision even more complex.

After he took a few hours, he still won’t work.

What is going to happen:

  • Download the data until you name captcha and enter the loop
  • Download captcha and show it to user, run QEventLoop by calling self.loop.exec_()
  • Exit QEventLoop by calling loop.quit() on the workflow slot, which is connected via self.line_edit.returnPressed.connect(self.worker.stop_waiting) in the main_window class
  • Check the captcha and the loop if the check fails, otherwise repeat the last URL that should be downloaded now, then go to the next URL

What's happening:

  • ... see above...

  • Exiting QEventLoop does not work. self.loop.isRunning() returns False after calling its exit() . self.isRunning returns True , so the thread does not seem to die under odd circumstances. However, the thread stops on the line self.loop.exec_() . So the thread is stuck executing an event loop, although the event loop tells me that it no longer works.

  • The graphical interface responds in the same way as the slots of the workflow class. I see that the text is sent to the worker thread, the state of the event loop and the thread itself, but nothing happens after the above line is executed.

The code is a bit confusing, so I add a bit of pseudo-python-mix code, leaving it immaterial:

 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 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_() 
+6
source share
2 answers

The slot runs inside the thread that QThread created, and not in the thread that controls QThread .

You need to move the QObject into the stream and connect its slot to the signal, and this slot will be executed inside the stream:

 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() 
+2
source

What you are describing is perfect for QWaitCondition .

A simple example:

 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_()) 
+3
source

Source: https://habr.com/ru/post/910761/


All Articles