小编典典

如何在不冻结PyQt GUI的情况下跟踪Python中的线程进度?

python

问题:

  1. 在不锁定GUI(“无响应”)的情况下跟踪胎面进度的最佳实践是什么?
  2. 通常,将线程应用于GUI开发的最佳实践是什么?

问题背景:

  • 我有Windows的PyQt GUI。
  • 它用于处理HTML文档集。
  • 处理一组文档大约需要三秒钟到三个小时。
  • 我希望能够同时处理多个集合。
  • 我不希望GUI锁定。
  • 我正在寻找实现这一目标的线程模块。
  • 我对线程技术比较陌生。
  • GUI有一个进度条。
  • 我希望它显示所选线程的进度。
  • 显示选定线程的结果(如果完成)。
  • 我正在使用Python 2.5。

我的想法: 更新进度时,线程会发出QtSignal,从而触发一些更新进度条的功能。在完成处理时也发出信号,以便可以显示结果。

#NOTE: this is example code for my idea, you do not have
#      to read this to answer the question(s).

import threading
from PyQt4 import QtCore, QtGui
import re
import copy

class ProcessingThread(threading.Thread, QtCore.QObject):

    __pyqtSignals__ = ( "progressUpdated(str)",
                        "resultsReady(str)")

    def __init__(self, docs):
        self.docs = docs
        self.progress = 0   #int between 0 and 100
        self.results = []
        threading.Thread.__init__(self)

    def getResults(self):
        return copy.deepcopy(self.results)

    def run(self):
        num_docs = len(self.docs) - 1
        for i, doc in enumerate(self.docs):
            processed_doc = self.processDoc(doc)
            self.results.append(processed_doc)
            new_progress = int((float(i)/num_docs)*100)

            #emit signal only if progress has changed
            if self.progress != new_progress:
                self.emit(QtCore.SIGNAL("progressUpdated(str)"), self.getName())
            self.progress = new_progress
            if self.progress == 100:
                self.emit(QtCore.SIGNAL("resultsReady(str)"), self.getName())

    def processDoc(self, doc):
        ''' this is tivial for shortness sake '''
        return re.findall('<a [^>]*>.*?</a>', doc)


class GuiApp(QtGui.QMainWindow):

    def __init__(self):
        self.processing_threads = {}  #{'thread_name': Thread(processing_thread)}
        self.progress_object = {}     #{'thread_name': int(thread_progress)}
        self.results_object = {}      #{'thread_name': []}
        self.selected_thread = ''     #'thread_name'

    def processDocs(self, docs):
        #create new thread
        p_thread = ProcessingThread(docs)
        thread_name = "example_thread_name"
        p_thread.setName(thread_name)
        p_thread.start()

        #add thread to dict of threads
        self.processing_threads[thread_name] = p_thread

        #init progress_object for this thread
        self.progress_object[thread_name] = p_thread.progress

        #connect thread signals to GuiApp functions
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('progressUpdated(str)'), self.updateProgressObject(thread_name))
        QtCore.QObject.connect(p_thread, QtCore.SIGNAL('resultsReady(str)'), self.updateResultsObject(thread_name))

    def updateProgressObject(self, thread_name):
        #update progress_object for all threads
        self.progress_object[thread_name] = self.processing_threads[thread_name].progress

        #update progress bar for selected thread
        if self.selected_thread == thread_name:
            self.setProgressBar(self.progress_object[self.selected_thread])

    def updateResultsObject(self, thread_name):
        #update results_object for thread with results
        self.results_object[thread_name] = self.processing_threads[thread_name].getResults()

        #update results widget for selected thread
        try:
            self.setResultsWidget(self.results_object[thread_name])
        except KeyError:
            self.setResultsWidget(None)

对此方法的任何评论(例如缺点,陷阱,赞美等)将不胜感激。

解析度:

我最终使用了QThread类以及相关的信号和插槽在线程之间进行通信。这主要是因为我的程序已经将Qt /
PyQt4用于GUI对象/小部件。此解决方案还需要对我现有的代码进行较少的更改即可实现。

这是指向适用的Qt文章的链接,该文章解释了Qt如何处理线程和信号,网址为http://www.linuxjournal.com/article/9602。摘录如下:

幸运的是,只要线程运行自己的事件循环,Qt允许跨线程连接信号和插槽。与发送和接收事件相比,这是一种更加干净的通信方法,因为它避免了所有簿记类和QEvent派生的中间类,这些类在任何平凡的应用程序中都变得必需。现在,线程之间的通信已成为将信号从一个线程连接到另一个线程中的插槽的问题,并且在线程之间交换数据的静音和线程安全问题由Qt处理。

为什么需要在要连接信号的每个线程中运行事件循环?原因与将信号从一个线程连接到另一个线程的插槽时,Qt使用的线程间通信机制有关。建立此类连接后,称为排队连接。当通过排队的连接发出信号时,将在下次执行目标对象的事件循环时调用该插槽。如果改为由另一个线程的信号直接调用该插槽,则该插槽将在与调用线程相同的上下文中执行。通常,这不是您想要的(特别是如果您使用数据库连接,则不是您想要的,因为数据库连接只能由创建它的线程使用)。排队的连接将信号正确分派到线程对象,并通过在事件系统上piggy带支持在其自己的上下文中调用其插槽。这正是我们希望线程间通信所需要的,其中一些线程正在处理数据库连接。Qt信号/时隙机制从根本上讲是上面概述的线程间事件传递方案的实现,但具有更简洁易用的界面。

注意: eliben 也有一个很好的答案,如果我不使用处理线程安全性和互斥的PyQt4,他的解决方案将是我的选择。


阅读 190

收藏
2020-12-20

共1个答案

小编典典

如果您想使用信号来指示主线程的进度,那么您实际上应该使用PyQt的QThread类而不是Python线程模块中的Thread类。

在PyQt Wiki上可以找到一个使用QThread,信号和插槽的简单示例:

https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots

2020-12-20