Pyqt5 qthread + sygnał nie działa + zamrożenie gui

Próbuję zrobić sprawdzanie skrzynki pocztowej z imap lib, działa całkiem dobrze z Pythonem, queue i multithread bez gui.

Ale kiedy próbuję umieścić gui, każdy fonction zrobiłem, zrobić gui zamrożenie aż do końca .

Próbowałem wielu rzeczy z różnych doc (dodaj qthread, signal, cursorr etcc) i tutoriale żaden nie działał dla mnie .

Czy ktoś może mi pomóc zrozumieć, jak ustawić lub dołączyć tekst do QtextEdit podczas uruchamiania funkcji, bo to działa dopiero po zakończeniu .

Oto Mój kod:

class Checker(QtCore.QThread):
    signal = QtCore.pyqtSignal(object)

    def __init__(self, lignesmailtocheck):
        QtCore.QThread.__init__(self)
        self.lignesmailtocheck = lignesmailtocheck

    def run(self):
            lignemailtocheck = self.lignesmailtocheck.strip()                        
            maillo, passo = lignemailtocheck.split(":",1)
            debmail, finmail = maillo.split("@",1)
            setimap =["oultook.com:imap-mail.outlook.com", "gmail.com:imap.gmail.com"]
            for lignesimaptocheck in sorted(setimap):
                    ligneimaptocheck = lignesimaptocheck.strip()
                    fai, imap = ligneimaptocheck.split(":",1)                                
                    if finmail == fai:
                            passo0 = passo.rstrip()
                            try :
                                    mail = imaplib.IMAP4_SSL(imap)
                                    mail.login(maillo, passo)
                                    mailboxok = open("MailBoxOk.txt", "a+", encoding='utf-8', errors='ignore')
                                    mailboxok.write(maillo+":"+passo+"\n")
                                    mailboxok.close()
                                    totaly = maillo+":"+passo0+":"+imap                                
                                    print(maillo+":"+passo+"\n")

                                    self.send_text.emit(totaly)
                                    time.sleep(1)
                            except imaplib.IMAP4.error:                          
                                           print ("LOGIN FAILED!!! ")
class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(400, 300)

        self.pushButton = QtWidgets.QPushButton(Form)
        self.pushButton.setGeometry(QtCore.QRect(150, 210, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton.clicked.connect(self.gogogo)

        self.openliste = QtWidgets.QToolButton(Form)
        self.openliste.setGeometry(QtCore.QRect(40, 110, 71, 21))
        self.openliste.setObjectName("openliste")

        self.textEdit = QtWidgets.QTextEdit(Form)
        self.textEdit.setGeometry(QtCore.QRect(170, 50, 201, 121))
        self.textEdit.setObjectName("textEdit")

        self.progressBar = QtWidgets.QProgressBar(Form)
        self.progressBar.setGeometry(QtCore.QRect(10, 260, 381, 23))
        self.progressBar.setValue(0)
        self.progressBar.setObjectName("progressBar")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.pushButton.setText(_translate("Form", "PushButton"))
        self.openliste.setText(_translate("Form", "..."))

    def gogogo(self):

        mailtocheck = open('File/toCheck.txt', 'r', encoding='utf-8', errors='ignore').readlines()        
        setmailtocheck = set(mailtocheck)
        for lignesmailtocheck in sorted(setmailtocheck):
            checker = Checker(lignesmailtocheck)

            thread = QThread()
            checker.moveToThread(thread)
            # connections after move so cross-thread:
            thread.started.connect(checker.run)
            checker.signal.connect(self.checkedok)
            thread.start()

    def checkedok(self, data):
        print(data)
        self.textEdit.append(data)
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())
Author: eyllanesc, 2017-01-08

3 answers

Ponieważ często pojawiają się pytania dotyczące używania QThread w PyQt, podobne do Twoich, oto przykład, który pokazuje, jak poprawnie używać wątków w PyQt. Mam nadzieję, że przyda się jako goto-odpowiedź na podobne pytania, więc spędziłem trochę więcej czasu niż zwykle przygotowując to.

Przykład tworzy wiele obiektów roboczych, które wykonują się w wątkach innych niż główne i komunikują się z głównym wątkiem (tj. GUI) za pomocą asynchronicznych sygnałów Qt.

import time
import sys

from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget


def trap_exc_during_debug(*args):
    # when app raises uncaught exception, print info
    print(args)


# install exception hook: without this, uncaught exception would cause application to exit
sys.excepthook = trap_exc_during_debug


class Worker(QObject):
    """
    Must derive from QObject in order to emit signals, connect slots to other signals, and operate in a QThread.
    """

    sig_step = pyqtSignal(int, str)  # worker id, step description: emitted every step through work() loop
    sig_done = pyqtSignal(int)  # worker id: emitted at end of work()
    sig_msg = pyqtSignal(str)  # message to be shown to user

    def __init__(self, id: int):
        super().__init__()
        self.__id = id
        self.__abort = False

    @pyqtSlot()
    def work(self):
        """
        Pretend this worker method does work that takes a long time. During this time, the thread's
        event loop is blocked, except if the application's processEvents() is called: this gives every
        thread (incl. main) a chance to process events, which in this sample means processing signals
        received from GUI (such as abort).
        """
        thread_name = QThread.currentThread().objectName()
        thread_id = int(QThread.currentThreadId())  # cast to int() is necessary
        self.sig_msg.emit('Running worker #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))

        for step in range(100):
            time.sleep(0.1)
            self.sig_step.emit(self.__id, 'step ' + str(step))

            # check if we need to abort the loop; need to process events to receive signals;
            app.processEvents()  # this could cause change to self.__abort
            if self.__abort:
                # note that "step" value will not necessarily be same for every thread
                self.sig_msg.emit('Worker #{} aborting work at step {}'.format(self.__id, step))
                break

        self.sig_done.emit(self.__id)

    def abort(self):
        self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
        self.__abort = True


class MyWidget(QWidget):
    NUM_THREADS = 5

    # sig_start = pyqtSignal()  # needed only due to PyCharm debugger bug (!)
    sig_abort_workers = pyqtSignal()

    def __init__(self):
        super().__init__()

        self.setWindowTitle("Thread Example")
        form_layout = QVBoxLayout()
        self.setLayout(form_layout)
        self.resize(400, 800)

        self.button_start_threads = QPushButton()
        self.button_start_threads.clicked.connect(self.start_threads)
        self.button_start_threads.setText("Start {} threads".format(self.NUM_THREADS))
        form_layout.addWidget(self.button_start_threads)

        self.button_stop_threads = QPushButton()
        self.button_stop_threads.clicked.connect(self.abort_workers)
        self.button_stop_threads.setText("Stop threads")
        self.button_stop_threads.setDisabled(True)
        form_layout.addWidget(self.button_stop_threads)

        self.log = QTextEdit()
        form_layout.addWidget(self.log)

        self.progress = QTextEdit()
        form_layout.addWidget(self.progress)

        QThread.currentThread().setObjectName('main')  # threads can be named, useful for log output
        self.__workers_done = None
        self.__threads = None

    def start_threads(self):
        self.log.append('starting {} threads'.format(self.NUM_THREADS))
        self.button_start_threads.setDisabled(True)
        self.button_stop_threads.setEnabled(True)

        self.__workers_done = 0
        self.__threads = []
        for idx in range(self.NUM_THREADS):
            worker = Worker(idx)
            thread = QThread()
            thread.setObjectName('thread_' + str(idx))
            self.__threads.append((thread, worker))  # need to store worker too otherwise will be gc'd
            worker.moveToThread(thread)

            # get progress messages from worker:
            worker.sig_step.connect(self.on_worker_step)
            worker.sig_done.connect(self.on_worker_done)
            worker.sig_msg.connect(self.log.append)

            # control worker:
            self.sig_abort_workers.connect(worker.abort)

            # get read to start worker:
            # self.sig_start.connect(worker.work)  # needed due to PyCharm debugger bug (!); comment out next line
            thread.started.connect(worker.work)
            thread.start()  # this will emit 'started' and start thread's event loop

        # self.sig_start.emit()  # needed due to PyCharm debugger bug (!)

    @pyqtSlot(int, str)
    def on_worker_step(self, worker_id: int, data: str):
        self.log.append('Worker #{}: {}'.format(worker_id, data))
        self.progress.append('{}: {}'.format(worker_id, data))

    @pyqtSlot(int)
    def on_worker_done(self, worker_id):
        self.log.append('worker #{} done'.format(worker_id))
        self.progress.append('-- Worker {} DONE'.format(worker_id))
        self.__workers_done += 1
        if self.__workers_done == self.NUM_THREADS:
            self.log.append('No more workers active')
            self.button_start_threads.setEnabled(True)
            self.button_stop_threads.setDisabled(True)
            # self.__threads = None

    @pyqtSlot()
    def abort_workers(self):
        self.sig_abort_workers.emit()
        self.log.append('Asking each worker to abort')
        for thread, worker in self.__threads:  # note nice unpacking by Python, avoids indexing
            thread.quit()  # this will quit **as soon as thread event loop unblocks**
            thread.wait()  # <- so you need to wait for it to *actually* quit

        # even though threads have exited, there may still be messages on the main thread's
        # queue (messages that threads emitted before the abort):
        self.log.append('All threads exited')


if __name__ == "__main__":
    app = QApplication([])

    form = MyWidget()
    form.show()

    sys.exit(app.exec_())

Główne pojęcia niezbędne do zrozumienie programowania wielowątkowego w PyQt jest następujące:

  • wątki Qt mają własną pętlę zdarzeń (specyficzną dla każdego wątku). Główny wątek, znany również jako wątek GUI, jest również QThread, a jego pętla zdarzeń jest zarządzana przez ten wątek.
  • sygnały pomiędzy wątkami są przesyłane (asynchronicznie)poprzez pętlę zdarzeń wątku odbierającego. Stąd responsywność GUI lub dowolnego wątku = zdolność do przetwarzania zdarzeń. Na przykład, jeśli wątek jest zajęty w pętli funkcji, nie może przetwarzać zdarzeń, więc nie będzie reagować na sygnały z GUI, dopóki funkcja nie powróci.
  • jeśli obiekt roboczy (metoda) w wątku może zmienić swój przebieg działania na podstawie sygnałów z GUI (np. w celu przerwania pętli lub oczekiwania), musi wywołać processEvents() Na instancji QApplication. Pozwoli to QThread przetwarzać zdarzenia, a tym samym wywoływać gniazda w odpowiedzi na sygnały asynchroniczne z GUI. Zauważ, że QApplication.instance().processEvents() wydaje się wywoływać processEvents() w każdym wątku, jeśli nie jest to pożądane, to {[7] } jest poprawne alternatywa.
  • wywołanie QThread.quit() nie zamyka od razu pętli zdarzeń: musi czekać na powrót aktualnie wykonującego gniazda (jeśli istnieje). Dlatego gdy wątek ma się zamknąć, musisz na nim czekać (). Tak więc przerwanie wątku roboczego zwykle wymaga sygnalizacji (poprzez sygnał Niestandardowy), aby zatrzymać cokolwiek robi: wymaga to niestandardowego sygnału na obiekcie GUI, połączenia tego sygnału z gniazdem roboczym, a metoda pracy workera musi wywołać wątek processEvents(), aby umożliwić emitowanemu sygnałowi dotarcie do gniazda. gniazdo podczas pracy.
 30
Author: Oliver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-14 19:30:36

Nie mogę przetestować, ponieważ setimap nie jest dostępny w moim systemie. Zmieniłem nazwę CheckerThread na Checker ponieważ nie jest już wątkiem (po prostu "żyje" w wątku):

class Checker(QtCore.QObject):

Następnie po prostu zamień zawartość pętli w {[4] } na to:

for lignesmailtocheck in sorted(setmailtocheck):
    checker = Checker(lignesmailtocheck)

    thread = QThread()
    checker.moveToThread(thread)
    # connections after move so cross-thread:
    thread.started.connect(checker.run)
    checker.signal.connect(self.checkedok)
    thread.start()

    self.threads.append(thread)

Prawie zawsze dobrym pomysłem jest dekorowanie slotów za pomocą pyqtSlot, więc zarówno run, jak i checkedok powinny być tak dekorowane.

Więc odpowiedź na temat wątków Qt {[18] } jest dość przydatna, aby przypomnieć sobie szczegóły (zauważ jednak, że używa połączeń w starym stylu -- musisz przetłumaczyć C++ connect( sender, SIGNAL(sig), receiver, SLOT(slot)); na PyQt5 sender.sig.connect(receiver.slot)).

 2
Author: Oliver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:15

Przepraszam za późną odpowiedź, ale jest to technika, która może rozwiązać podobne problemy.

Problem jest jasny. GUI zawiesza się, ponieważ jego wątek musi wykonać inną pracę. Abstrakcyjne (z punktu PyQt) rozwiązanie jest podane poniżej:
  1. tworzy klasę dziedziczącą z wątku.Wątek, który będzie pracownikiem.
  2. podaj konstruktorowi kolejkę (queue.Kolejka) jako środek komunikacji.
  3. możesz uruchomić wątek roboczy z wątku GUI i przekazać wiadomości korzystanie z kolejki.
  4. aby wątek GUI odczytał wiadomości, utwórz QTimer z wybranym interwałem i zarejestruj funkcję zwrotną. W funkcji callback odczytaj kolejkę.

Przykładowy Kod:

class Worker(threading.Thread):

    def __init__(self, queue):
        super().init()
        self.queue = queue

    def run(self):
         # Your code that uses self.queue.put(object)

class Gui:

    def __init__(self):
        self.timer = Qtimer()
        self.timer.setInterval(milliseconds)
        self.timer.timeout.connect(self.read_data)


    def start_worker(self):
        self.queue = queue.Queue()

        thr = Worker(self.queue)

        thr.start()


    def read_data(self):
        data = self.queue.get()

Self.timer.przerwa.connect rejestruje funkcję wywołania zwrotnego.

 0
Author: Vaggos Phl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-18 12:34:05