Pyqt5 qthread + сигнал не работает + замораживание gui
Я пытаюсь сделать проверку почтового ящика с imap
lib, он отлично работает с python, queue и multithread без gui.
но когда я пытаюсь поставить gui, каждый fonction я сделал, сделать gui заморозить до конца .
Я пробовал много вещей из разных doc (добавить qthread, signal, cursorr etcc) и учебники, которые не работали для меня .
может кто-нибудь помочь мне понять, как установить или добавить текст в QtextEdit при запуске функции, потому что она работает только после финиша .
вот мой код :
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 ответов
так как часто возникают вопросы об использовании QThread
в PyQt, аналогично вашему, вот пример, который показывает, как правильно использовать потоки в PyQt. Я надеюсь, что это может быть полезно в качестве Гото-ответа на подобные вопросы, поэтому я потратил немного больше времени, чем обычно, готовя это.
в примере создается ряд рабочих объектов, которые выполняются в неосновных потоках и взаимодействуют с основным потоком (т. е. GUI) через асинхронные сигналы 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_())
главная понятия, необходимые для понимания многопоточного программирования в PyQt, следующие:
- потоки Qt имеют свой собственный цикл событий (специфичный для каждого потока). Основной поток, он же поток GUI, также является
QThread
, и его цикл событий управляется этим потоком. - сигналы между потоками передаются (асинхронно) через цикл событий принимающего потока. Следовательно, отзывчивость GUI или любой поток = способность обрабатывать события. Е. Г., если поток занят в цикл функции, он не может обрабатывать события, поэтому он не будет отвечать на сигналы от GUI, пока функция не вернется.
- если рабочий объект (метод) в потоке может изменить свой курс действия на основе сигналов от GUI (скажем, прервать цикл или ожидание), он должен вызвать
processEvents()
наQApplication
экземпляра. Это позволит QThread обрабатывать события и, следовательно, вызывать слоты в ответ на асинхронные сигналы от GUI. Обратите внимание, чтоQApplication.instance().processEvents()
кажется, назвалprocessEvents()
на каждый нить, если это не желательно, тоQThread.currentThread().processEvents()
является достойной альтернативой. - вызов
QThread.quit()
не сразу выходит из цикла событий: он должен ждать возврата текущего исполняемого слота (если таковой имеется). Следовательно, как только потоку говорят выйти, вы должны ждать() на нем. Таким образом, прерывание рабочего потока обычно включает в себя сигнализацию (через пользовательский сигнал), чтобы остановить все, что он делает: для этого требуется пользовательский сигнал на объекте GUI, соединение этого сигнала с рабочим слотом и рабочий метод работы необходимо позвонить позволить испущенному сигналу достигнуть слот пока делающ работу.
Я не могу проверить, потому что setimap недоступен в моей системе. Я переименовал CheckerThread
to Checker
поскольку это больше не поток (он просто "живет" в потоке):
class Checker(QtCore.QObject):
тогда просто замените содержимое цикла в gogogo(self)
С этого:
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)
это почти всегда хорошая идея, чтобы украсить слоты с pyqtSlot
так как run
и checkedok
должно быть таким образом украшено.
на Итак, ответьте о потоках Qt вполне удобно напомните себе о деталях (обратите внимание, однако, что он использует соединения старого стиля - вы должны перевести C++ connect( sender, SIGNAL(sig), receiver, SLOT(slot));
на PyQt5 sender.sig.connect(receiver.slot)
).
извините за поздний ответ, но это метод, который может решить подобные проблемы.
проблема понятна. GUI зависает, потому что его поток должен выполнить другую работу. Ниже приведено абстрактное (из точки PyQt) решение:
- создайте класс, наследующий от threading.Поток, который будет рабочим.
- передайте конструктору очередь (queue.Очередь) как средство связи.
- вы можете запустить рабочий поток из GUI поток и передача сообщений с помощью очереди.
- чтобы поток GUI читал сообщения, создайте QTimer с интервалом по вашему выбору и зарегистрируйте функцию обратного вызова. В функции обратного вызова прочитайте очередь.
Пример Кода:
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.таймер.перерыв.подключить регистрирует функцию обратного вызова.