2012-03-15 31 views
6

Tôi quyết định thêm GUI vào một trong các tập lệnh của mình. Kịch bản là một trình duyệt web đơn giản. Tôi quyết định sử dụng một chuỗi công nhân khi tải xuống và phân tích cú pháp dữ liệu có thể mất một lúc. Tôi quyết định sử dụng PySide, nhưng kiến ​​thức của tôi về Qt nói chung là khá hạn chế.PySide chờ tín hiệu từ chủ đề chính trong luồng công nhân

Vì kịch bản được yêu cầu phải chờ người dùng nhập khi gặp một hình ảnh xác thực, tôi phải đợi cho đến khi QLineEdit kích hoạt returnPressed và gửi nội dung đó đến chuỗi công nhân để nó có thể gửi nó để xác thực. Điều đó nên được tốt hơn so với bận rộn chờ đợi cho các phím trở lại được ép.

Dường như chờ tín hiệu không thẳng như tôi nghĩ và sau khi tìm kiếm một lúc, tôi đã xem một số giải pháp tương tự như this. Việc báo hiệu qua các luồng và một vòng lặp sự kiện cục bộ trong luồng công nhân làm cho giải pháp của tôi phức tạp hơn một chút.

Sau khi mày mò với nó trong vài giờ nó vẫn không hoạt động.

gì là vụ xảy ra:

  • Tải dữ liệu cho đến khi giới thiệu đến hình ảnh xác thực và nhập một vòng lặp
  • Tải hình ảnh xác thực và hiển thị nó cho người dùng, bắt đầu QEventLoop bằng cách gọi self.loop.exec_()
  • Thoát QEventLoop bằng cách gọi loop.quit() trong khe chủ đề công nhân được kết nối qua self.line_edit.returnPressed.connect(self.worker.stop_waiting) trong các lớp main_window
  • Xác thực captcha và vòng lặp nếu validati trên thất bại, nếu không thử lại url cuối cùng mà nên tải về bây giờ, sau đó di chuyển về với url tới

gì xảy ra:

  • ... xem ở trên ...

  • Thoát khỏi QEventLoop không hoạt động. self.loop.isRunning() trả lại False sau khi gọi số exit(). self.isRunning trả về True, vì vậy chủ đề dường như không chết trong những trường hợp kỳ lạ. Chuỗi vẫn dừng ở đường dây self.loop.exec_(). Vì vậy, thread bị mắc kẹt khi thực thi vòng lặp sự kiện mặc dù vòng lặp sự kiện cho tôi biết nó không còn chạy nữa.

  • GUI phản hồi như các vị trí của lớp chuỗi công nhân. Tôi có thể thấy các văn bản beeing gửi đến thread công nhân, tình trạng của vòng lặp sự kiện và các chủ đề chính nó, nhưng không có gì sau khi dòng đề cập ở trên được thực hiện.

Mã này là một chút phức tạp, như vậy tôi thêm một chút pseudo-code-python-mix rời ra không quan trọng:

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_() 

Trả lời

2

Khe cắm được thực hiện bên trong các chủ đề đã tạo dựng nên QThread, và không có trong thread rằng QThread điều khiển.

Bạn cần phải di chuyển một QObject để sợi và kết nối khe của nó đến tín hiệu, và khe cắm đó sẽ được thực hiện bên trong các chủ đề:

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() 
+0

Thật vậy, mà giải quyết vấn đề của tôi. Cảm ơn. –

3

Những gì bạn đang mô tả ngoại hình lý tưởng cho QWaitCondition.

đơn giản ví dụ:

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_()) 
Các vấn đề liên quan