如何在不冻结PyQt GUI的情况下跟踪Python中的线程进度? [英] How to keep track of thread progress in Python without freezing the PyQt GUI?

查看:66
本文介绍了如何在不冻结PyQt GUI的情况下跟踪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使用的通信机制 当连接来自一个的信号时 线程到另一个线程的插槽. 建立这样的连接后, 称为排队连接. 当信号通过 排队的连接,插槽被调用 下次目标对象 事件循环被执行.如果插槽 而是直接由 来自另一个线程的信号,该插槽 将在与相同的上下文中执行 调用线程.通常这是 不是你想要的(尤其是不是 如果您要使用的是 数据库连接,作为数据库 连接只能由 创建它的线程).排队 连接正确调度 向线程对象发出信号并 通过以下方式在自己的上下文中调用其广告位 支持事件系统. 这正是我们想要的 线程间通信在其中 一些线程正在处理 数据库连接. Qt 信号/插槽机制从根本上 线程间的实现 上面概述的事件传递方案, 但要干净得多 易于使用的界面.

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

解决方案

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

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

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

Questions:

  1. What is the best practice for keeping track of a tread's progress without locking the GUI ("Not Responding")?
  2. Generally, what are the best practices for threading as it applies to GUI development?

Question Background:

  • I have a PyQt GUI for Windows.
  • It is used to process sets of HTML documents.
  • It takes anywhere from three seconds to three hours to process a set of documents.
  • I want to be able to process multiple sets at the same time.
  • I don't want the GUI to lock.
  • I'm looking at the threading module to achieve this.
  • I am relatively new to threading.
  • The GUI has one progress bar.
  • I want it to display the progress of the selected thread.
  • Display results of the selected thread if it's finished.
  • I'm using Python 2.5.

My Idea: Have the threads emit a QtSignal when the progress is updated that triggers some function that updates the progress bar. Also signal when finished processing so results can be displayed.

#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)

Any commentary on this approach (e.g. drawbacks, pitfalls, praises, etc.) will be appreciated.

Resolution:

I ended up using the QThread class and associated signals and slots to communicate between threads. This is primarily because my program already uses Qt/PyQt4 for the GUI objects/widgets. This solution also required fewer changes to my existing code to implement.

Here is a link to an applicable Qt article that explains how Qt handles threads and signals, http://www.linuxjournal.com/article/9602. Excerpt below:

Fortunately, Qt permits signals and slots to be connected across threads—as long as the threads are running their own event loops. This is a much cleaner method of communication compared to sending and receiving events, because it avoids all the bookkeeping and intermediate QEvent-derived classes that become necessary in any nontrivial application. Communicating between threads now becomes a matter of connecting signals from one thread to the slots in another, and the mutexing and thread-safety issues of exchanging data between threads are handled by Qt.

Why is it necessary to run an event loop within each thread to which you want to connect signals? The reason has to do with the inter-thread communication mechanism used by Qt when connecting signals from one thread to the slot of another thread. When such a connection is made, it is referred to as a queued connection. When signals are emitted through a queued connection, the slot is invoked the next time the destination object's event loop is executed. If the slot had instead been invoked directly by a signal from another thread, that slot would execute in the same context as the calling thread. Normally, this is not what you want (and especially not what you want if you are using a database connection, as the database connection can be used only by the thread that created it). The queued connection properly dispatches the signal to the thread object and invokes its slot in its own context by piggy-backing on the event system. This is precisely what we want for inter-thread communication in which some of the threads are handling database connections. The Qt signal/slot mechanism is at root an implementation of the inter-thread event-passing scheme outlined above, but with a much cleaner and easier-to-use interface.

NOTE: eliben also has a good answer, and if I weren't using PyQt4, which handles thread-safety and mutexing, his solution would have been my choice.

解决方案

If you want to use signals to indicate progress to the main thread then you should really be using PyQt's QThread class instead of the Thread class from Python's threading module.

A simple example which uses QThread, signals and slots can be found on the PyQt Wiki:

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

这篇关于如何在不冻结PyQt GUI的情况下跟踪Python中的线程进度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆