PyQt:如何在不冻结 GUI 的情况下更新进度? [英] PyQt: How to update progress without freezing the GUI?

查看:40
本文介绍了PyQt:如何在不冻结 GUI 的情况下更新进度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  1. 什么是最佳实践跟踪线程的在不锁定 GUI 的情况下取得进展(无响应")?
  2. 一般来说,最佳做法是什么线程,因为它适用于 GUI发展?

问题背景:

  • 我有一个适用于 Windows 的 PyQt GUI.
  • 用于处理 HTML 集文件.
  • 大约需要三秒钟到三个小时来处理一组文件.
  • 我希望能够处理同时进行多组.
  • 我不想锁定 GUI.
  • 我正在查看线程模块以实现这一目标.
  • 我对线程比较陌生.
  • GUI 有一个进度条.
  • 我想让它显示进度选定的线程.
  • 显示选中的结果线程如果完成.
  • 我使用的是 Python 2.5.
  • 我的想法:让线程在进度更新时发出 QtSignal,触发一些更新进度条的函数.处理完成时也会发出信号,以便显示结果.

    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.

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

    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.

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

    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:

    幸运的是,Qt 允许要连接的信号和插槽跨线程——只要线程正在运行自己的事件循环.这是一种更清洁的方法通信与发送和接收事件,因为它避免了所有的簿记和中间QEvent 派生类变为在任何重要的应用.之间的交流线程现在成为一个问题将信号从一个线程连接到另一个插槽,以及互斥和交换的线程安全问题线程之间的数据由Qt.

    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.

    为什么需要举办活动在您访问的每个线程中循环想要连接信号?原因与线程间有关Qt使用的通信机制当连接来自一个的信号时线程到另一个线程的插槽.当建立这样的连接时,它是称为排队连接.当信号通过一个排队连接,插槽被调用下次目标对象的事件循环被执行.如果插槽而是由一个直接调用来自另一个线程的信号,那个插槽将在相同的上下文中执行调用线程.通常,这是不是你想要的(尤其不是你想要的如果你使用的是你想要什么数据库连接,作为数据库连接只能由创建它的线程).排队的连接正确分派向线程对象发出信号和通过在自己的上下文中调用其插槽事件系统的捎带.这正是我们想要的线程间通信,其中一些线程正在处理数据库连接.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.

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

    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.

    推荐答案

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

    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.

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

    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 的情况下更新进度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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