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_())
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 instancjiQApplication
. Pozwoli to QThread przetwarzać zdarzenia, a tym samym wywoływać gniazda w odpowiedzi na sygnały asynchroniczne z GUI. Zauważ, żeQApplication.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ątekprocessEvents()
, aby umożliwić emitowanemu sygnałowi dotarcie do gniazda. gniazdo podczas pracy.
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.
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:- tworzy klasę dziedziczącą z wątku.Wątek, który będzie pracownikiem.
- podaj konstruktorowi kolejkę (queue.Kolejka) jako środek komunikacji.
- możesz uruchomić wątek roboczy z wątku GUI i przekazać wiadomości korzystanie z kolejki.
- 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.
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