سؤال

قررت إضافة واجهة المستخدم الرسومية إلى أحد البرامج النصية الخاصة بي.البرنامج النصي عبارة عن مكشطة ويب بسيطة.قررت استخدام مؤشر ترابط عامل لأن تنزيل البيانات وتحليلها قد يستغرق بعض الوقت.قررت استخدام PySide، لكن معرفتي بـ Qt بشكل عام محدودة جدًا.

نظرًا لأنه من المفترض أن ينتظر البرنامج النصي إدخال المستخدم عند ظهور رمز التحقق، فقد قررت أنه يجب الانتظار حتى QLineEdit حرائق returnPressed ثم أرسل محتواه إلى مؤشر ترابط العامل حتى يتمكن من إرساله للتحقق من صحته.يجب أن يكون ذلك أفضل من انتظار الانشغال حتى يتم الضغط على مفتاح العودة.

يبدو أن انتظار الإشارة ليس بالأمر السهل كما اعتقدت، وبعد البحث لفترة وجدت عدة حلول مشابهة لـ هذا.إن الإشارة عبر سلاسل الرسائل وحلقة الأحداث المحلية في مؤشر ترابط العامل تجعل الحل الخاص بي أكثر تعقيدًا بعض الشيء.

بعد التلاعب به لعدة ساعات، فإنه لا يزال لا يعمل.

ما الذي من المفترض أن يحدث:

  • قم بتنزيل البيانات حتى تتم الإشارة إلى رمز التحقق وأدخل حلقة
  • قم بتنزيل كلمة التحقق وعرضها للمستخدم، ابدأ QEventLoop بالاتصال self.loop.exec_()
  • مخرج QEventLoop بالاتصال loop.quit() في فتحة مؤشرات الترابط العاملة التي يتم توصيلها عبر self.line_edit.returnPressed.connect(self.worker.stop_waiting) في ال main_window فصل
  • قم بالتحقق من صحة كلمة التحقق والتكرار في حالة فشل التحقق من الصحة، وإلا أعد محاولة استخدام عنوان url الأخير الذي يجب أن يكون قابلاً للتنزيل الآن، ثم انتقل إلى عنوان url التالي

ما يحدث:

  • ...أنظر فوق...

  • الخروج QEventLoop لا يعمل. self.loop.isRunning() عائدات False بعد الاتصال بها exit(). self.isRunning عائدات True, ، على هذا النحو لا يبدو أن الخيط يموت في ظل ظروف غريبة.لا يزال الخيط يتوقف عند self.loop.exec_() خط.على هذا النحو، فإن مؤشر الترابط عالق في تنفيذ حلقة الحدث على الرغم من أن حلقة الحدث تخبرني أنها لم تعد قيد التشغيل.

  • تستجيب واجهة المستخدم الرسومية كما تفعل فتحات فئة مؤشر الترابط العامل.أستطيع أن أرى النص الذي يتم إرساله إلى مؤشر ترابط العامل، وحالة حلقة الحدث والخيط نفسه، ولكن لا شيء بعد تنفيذ السطر المذكور أعلاه.

الكود معقد بعض الشيء، لذلك أقوم بإضافة القليل من مزيج الكود الزائف بيثون مع ترك ما هو غير مهم:

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_())
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top