栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

pyqt5 qthread +信号不起作用+ GUI冻结

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

pyqt5 qthread +信号不起作用+ GUI冻结

由于

QThread
与您的情况类似,在PyQt中经常存在有关使用的问题,因此以下示例显示了如何在PyQt中正确使用线程。我希望它可以用作类似问题的解答,因此我比平常花了更多的时间准备它。

该示例创建了多个工作对象,这些工作对象在非主线程中执行,并通过Qt的异步信号与主(即GUI)线程进行通信。

import timeimport sysfrom PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlotfrom PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidgetdef 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 exitsys.excepthook = trap_exc_during_debugclass 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 = Trueclass 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对象上的自定义信号,该信号的一个工人插槽的连接,和工人的工作方法必须调用线程的
    processEvents()
    到在工作时允许发射的信号到达插槽。


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/617631.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号