tgr*_*ray 19 python user-interface multithreading pyqt
我的想法:让线程在更新进度时发出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)
Run Code Online (Sandbox Code Playgroud)
任何关于这种方法的评论(例如缺点,陷阱,赞美等)都将受到赞赏.
我最终使用QThread类和相关的信号和插槽来在线程之间进行通信.这主要是因为我的程序已经将Qt/PyQt4用于GUI对象/小部件.此解决方案还需要对现有代码进行较少的更改才能实现.
以下是一篇适用的Qt文章的链接,该文章解释了Qt如何处理线程和信号,http://www.linuxjournal.com/article/9602.摘录如下:
幸运的是,只要线程正在运行自己的事件循环,Qt就允许跨线程连接信号和插槽.与发送和接收事件相比,这是一种更清晰的通信方法,因为它避免了在任何重要应用程序中必需的所有簿记和中间QEvent派生类.现在,线程之间的通信变成了将信号从一个线程连接到另一个线程中的槽的问题,并且线程之间交换数据的静音和线程安全问题由Qt处理.
为什么有必要在每个要连接信号的线程中运行事件循环?原因与Qt在将信号从一个线程连接到另一个线程的槽时使用的线程间通信机制有关.当建立这样的连接时,它被称为排队连接.当通过排队连接发出信号时,下次执行目标对象的事件循环时将调用该槽.如果插槽已被另一个线程的信号直接调用,则该插槽将在与调用线程相同的上下文中执行.通常情况下,这不是您想要的(尤其不是您在使用数据库连接时所需的内容,因为数据库连接只能由创建它的线程使用).排队连接正确地将信号调度到线程对象,并通过在事件系统上捎带来在其自己的上下文中调用其插槽.这正是我们想要的线程间通信,其中一些线程正在处理数据库连接.Qt信号/插槽机制在根本上是上面概述的线程间事件传递方案的实现,但具有更清洁和更易于使用的接口.
注意: eliben也有一个很好的答案,如果我没有使用PyQt4,它处理线程安全和静音,他的解决方案将是我的选择.
小智 9
如果你想使用信号来指示主线程的进度,那么你应该使用PyQt的QThread类而不是Python的线程模块中的Thread类.
在PyQt Wiki上可以找到一个使用QThread,信号和插槽的简单示例:
https://wiki.python.org/moin/PyQt/Threading,_Signals_and_Slots
小智 5
原生python队列不起作用,因为你必须阻塞队列get(),这会阻塞你的UI.
Qt基本上在内部实现了一个排队系统,用于跨线程通信.从任何线程尝试此调用以将呼叫发布到插槽.
QtCore.QMetaObject.invokeMethod()
它很笨拙,文档记录很差,但它应该从非Qt线程做你想做的事情.
您也可以使用事件机制.请参阅QApplication(或QCoreApplication)以获取名为"post"的方法.
编辑:这是一个更完整的例子......
我基于QWidget创建了自己的类.它有一个接受字符串的插槽; 我这样定义:
@QtCore.pyqtSlot(str)
def add_text(self, text):
...
Run Code Online (Sandbox Code Playgroud)
稍后,我在主GUI线程中创建此小部件的实例.从主GUI线程或任何其他线程(敲木头)我可以调用:
QtCore.QMetaObject.invokeMethod(mywidget, "add_text", QtCore.Q_ARG(str,"hello world"))
Run Code Online (Sandbox Code Playgroud)
笨重,但它让你在那里.
担.