問題描述
我正在嘗試使用 imap
lib 制作郵箱檢查器,它在沒有 gui 的情況下與 python、隊列和多線程一起工作得很好.
I am trying to make a mailbox checker with imap
lib, it work pretty fine with python, queue and multithread without gui.
但是當我嘗試放置一個 gui 時,我所做的每一個功能,都讓 gui 凍結直到完成.
But when I try to put a gui, every fonction i made, make the gui freeze until finish .
我從各種文檔(添加 qthread、signal、cursor 等)和教程中嘗試了很多東西,但對我沒有用.
I tried many thing from various doc(add qthread, signal, cursorr etcc) and tutorials none worked for me .
有人可以幫助我了解如何在運行函數時將文本設置或附加到 QtextEdit,因為它只有在完成后才能工作.
Can someone help me to understand how to set or append a text to a QtextEdit while running a function coz it work only after finish .
這是我的代碼:
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+"
")
mailboxok.close()
totaly = maillo+":"+passo0+":"+imap
print(maillo+":"+passo+"
")
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_())
推薦答案
由于在PyQt中使用QThread
經常會遇到問題,和你的類似,這里舉個例子說明如何正確使用線程在 PyQt 中.我希望它可以作為類似問題的轉到答案有用,所以我花了比平時更多的時間來準備這個.
Since there are often questions about using QThread
in PyQt, similar to yours, here is an example that shows how to correctly use threads in PyQt. I'm hoping it can be useful as a goto-answer for similar questions so I spent a bit more time than usual preparing this.
該示例創建了許多在非主線程中執行的工作對象,并通過 Qt 的異步信號與主(即 GUI)線程進行通信.
The example creates a number of worker objects that execute in non-main threads and communicate with the main (ie GUI) thread via Qt's asynchronous signals.
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 中多線程編程所必需的主要概念如下:
The main concepts necessary to understand multi-thread programming in PyQt are the following:
- Qt 線程有自己的事件循環(特定于每個線程).主線程,也就是 GUI 線程,也是一個
QThread
,它的事件循環由那個線程管理. - 線程之間的信號通過接收線程的事件循環(異步)傳輸.因此,GUI 或任何線程的響應能力 = 處理事件的能力.例如,如果一個線程在函數循環中忙,它就不能處理事件,所以在函數返回之前它不會響應來自 GUI 的信號.
- 如果線程中的工作對象(方法)可能必須根據來自 GUI 的信號更改其操作過程(例如,中斷循環或等待),它必須調用
processEvents()
QApplication
實例上的 code>.這將允許 QThread 處理事件,從而調用槽以響應來自 GUI 的異步信號.請注意,QApplication.instance().processEvents()
似乎在每個線程上調用processEvents()
,如果不需要,則使用QThread.currentThread().processEvents()
是一個有效的替代方案. - 對
QThread.quit()
的調用不會立即退出其事件循環:它必須等待當前正在執行的槽(如果有)返回.因此,一旦一個線程被告知要退出,您必須在其上等待().因此,中止工作線程通常涉及(通過自定義信號)發出信號以停止它正在做的任何事情:這需要 GUI 對象上的自定義信號,將該信號連接到工作槽,并且工作方法必須調用線程的processEvents()
以允許發出的信號在工作時到達插槽.
- Qt threads have their own event loop (specific to each thread). The main thread, aka the GUI thread, is also a
QThread
, and its event loop is managed by that thread. - Signals between threads are transmitted (asynchronously) via the receiving thread's event loop. Hence responsiveness of GUI or any thread = ability to process events. E.g., if a thread is busy in a function loop, it can't process events, so it won't respond to signals from the GUI until the function returns.
- If a worker object (method) in a thread may have to change its course of action based on signals from the GUI (say, to interrupt a loop or a wait), it must call
processEvents()
on theQApplication
instance. This will allow the QThread to process events, and hence to call slots in response to async signals from the GUI. Note thatQApplication.instance().processEvents()
seems to callprocessEvents()
on every thread, if this is not desired thenQThread.currentThread().processEvents()
is a valid alternative. - A call to
QThread.quit()
does not immediately quit its event loop: it must wait for currently executing slot (if any) to return. Hence once a thread is told to quit, you must wait() on it. So aborting a worker thread usually involves signaling it (via a custom signal) to stop whatever it is doing: this requires a custom signal on a GUI object, a connection of that signal to a worker slot, and worker work method must call thread'sprocessEvents()
to allow the emitted signal to reach the slot while doing work.
這篇關于Pyqt5 qthread + 信號不工作 + gui 凍結的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!