PyQT线程的最简单方法
Simplest way for PyQT Threading
我在 PyQt 中有一个带有函数 addImage(image_path)
的 GUI。很容易想象,当一个新图像应该被添加到一个 QListWidget 中时它被调用。为了检测文件夹中的新图像,我使用 threading.Thread
和 watchdog
来检测文件夹中的文件更改,然后该线程直接调用 addImage
。
出于线程安全的原因,这会产生 QPixmap
不应在 gui 线程外调用的警告。
使这个线程安全的最好和最简单的方法是什么?问线程?信号/槽? QMetaObject.invokeMethod?我只需要将一个字符串从线程传递到 addImage
.
我认为最好的方法是使用 signal/slot 机制。这是一个例子。 (注意:请参阅下面的 EDIT,它指出了我的方法中可能存在的弱点)。
from PyQt4 import QtGui
from PyQt4 import QtCore
# Create the class 'Communicate'. The instance
# from this class shall be used later on for the
# signal/slot mechanism.
class Communicate(QtCore.QObject):
myGUI_signal = QtCore.pyqtSignal(str)
''' End class '''
# Define the function 'myThread'. This function is the so-called
# 'target function' when you create and start your new Thread.
# In other words, this is the function that will run in your new thread.
# 'myThread' expects one argument: the callback function name. That should
# be a function inside your GUI.
def myThread(callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.myGUI_signal.connect(callbackFunc)
# Endless loop. You typically want the thread
# to run forever.
while(True):
# Do something useful here.
msgForGui = 'This is a message to send to the GUI'
mySrc.myGUI_signal.emit(msgForGui)
# So now the 'callbackFunc' is called, and is fed with 'msgForGui'
# as parameter. That is what you want. You just sent a message to
# your GUI application! - Note: I suppose here that 'callbackFunc'
# is one of the functions in your GUI.
# This procedure is thread safe.
''' End while '''
''' End myThread '''
在您的 GUI 应用程序代码中,您应该创建新线程,为其提供正确的回调函数,并使其成为 运行。
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
import os
# This is the main window from my GUI
class CustomMainWindow(QtGui.QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
self.setGeometry(300, 300, 2500, 1500)
self.setWindowTitle("my first window")
# ...
self.startTheThread()
''''''
def theCallbackFunc(self, msg):
print('the thread has sent this message to the GUI:')
print(msg)
print('---------')
''''''
def startTheThread(self):
# Create the new thread. The target function is 'myThread'. The
# function we created in the beginning.
t = threading.Thread(name = 'myThread', target = myThread, args = (self.theCallbackFunc))
t.start()
''''''
''' End CustomMainWindow '''
# This is the startup code.
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''' End Main '''
编辑
先生three_pineapples 和 Brendan Abel 先生指出了我方法中的一个弱点。事实上,该方法适用于这种特殊情况,因为您直接生成/发射信号。当您处理按钮和小部件上的内置 Qt 信号时,您应该采用另一种方法(如 Brendan Abel 先生的回答所述)。
先生three_pineapples 建议我在 Whosebug 中开始一个新主题,以比较使用 GUI 进行线程安全通信的几种方法。我会深入研究此事,明天再做:-)
您应该使用 Qt 提供的内置 QThread
。您可以将文件监控代码放在继承自 QObject
的 worker class 中,以便它可以使用 Qt Signal/Slot 系统在两者之间传递消息线程。
class FileMonitor(QObject):
image_signal = QtCore.pyqtSignal(str)
@QtCore.pyqtSlot()
def monitor_images(self):
# I'm guessing this is an infinite while loop that monitors files
while True:
if file_has_changed:
self.image_signal.emit('/path/to/image/file.jpg')
class MyWidget(QtGui.QWidget):
def __init__(self, ...)
...
self.file_monitor = FileMonitor()
self.thread = QtCore.QThread(self)
self.file_monitor.image_signal.connect(self.image_callback)
self.file_monitor.moveToThread(self.thread)
self.thread.started.connect(self.file_monitor.monitor_images)
self.thread.start()
@QtCore.pyqtSlot(str)
def image_callback(self, filepath):
pixmap = QtGui.QPixmap(filepath)
...
我在 PyQt 中有一个带有函数 addImage(image_path)
的 GUI。很容易想象,当一个新图像应该被添加到一个 QListWidget 中时它被调用。为了检测文件夹中的新图像,我使用 threading.Thread
和 watchdog
来检测文件夹中的文件更改,然后该线程直接调用 addImage
。
出于线程安全的原因,这会产生 QPixmap
不应在 gui 线程外调用的警告。
使这个线程安全的最好和最简单的方法是什么?问线程?信号/槽? QMetaObject.invokeMethod?我只需要将一个字符串从线程传递到 addImage
.
我认为最好的方法是使用 signal/slot 机制。这是一个例子。 (注意:请参阅下面的 EDIT,它指出了我的方法中可能存在的弱点)。
from PyQt4 import QtGui
from PyQt4 import QtCore
# Create the class 'Communicate'. The instance
# from this class shall be used later on for the
# signal/slot mechanism.
class Communicate(QtCore.QObject):
myGUI_signal = QtCore.pyqtSignal(str)
''' End class '''
# Define the function 'myThread'. This function is the so-called
# 'target function' when you create and start your new Thread.
# In other words, this is the function that will run in your new thread.
# 'myThread' expects one argument: the callback function name. That should
# be a function inside your GUI.
def myThread(callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.myGUI_signal.connect(callbackFunc)
# Endless loop. You typically want the thread
# to run forever.
while(True):
# Do something useful here.
msgForGui = 'This is a message to send to the GUI'
mySrc.myGUI_signal.emit(msgForGui)
# So now the 'callbackFunc' is called, and is fed with 'msgForGui'
# as parameter. That is what you want. You just sent a message to
# your GUI application! - Note: I suppose here that 'callbackFunc'
# is one of the functions in your GUI.
# This procedure is thread safe.
''' End while '''
''' End myThread '''
在您的 GUI 应用程序代码中,您应该创建新线程,为其提供正确的回调函数,并使其成为 运行。
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
import os
# This is the main window from my GUI
class CustomMainWindow(QtGui.QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
self.setGeometry(300, 300, 2500, 1500)
self.setWindowTitle("my first window")
# ...
self.startTheThread()
''''''
def theCallbackFunc(self, msg):
print('the thread has sent this message to the GUI:')
print(msg)
print('---------')
''''''
def startTheThread(self):
# Create the new thread. The target function is 'myThread'. The
# function we created in the beginning.
t = threading.Thread(name = 'myThread', target = myThread, args = (self.theCallbackFunc))
t.start()
''''''
''' End CustomMainWindow '''
# This is the startup code.
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
''' End Main '''
编辑
先生three_pineapples 和 Brendan Abel 先生指出了我方法中的一个弱点。事实上,该方法适用于这种特殊情况,因为您直接生成/发射信号。当您处理按钮和小部件上的内置 Qt 信号时,您应该采用另一种方法(如 Brendan Abel 先生的回答所述)。
先生three_pineapples 建议我在 Whosebug 中开始一个新主题,以比较使用 GUI 进行线程安全通信的几种方法。我会深入研究此事,明天再做:-)
您应该使用 Qt 提供的内置 QThread
。您可以将文件监控代码放在继承自 QObject
的 worker class 中,以便它可以使用 Qt Signal/Slot 系统在两者之间传递消息线程。
class FileMonitor(QObject):
image_signal = QtCore.pyqtSignal(str)
@QtCore.pyqtSlot()
def monitor_images(self):
# I'm guessing this is an infinite while loop that monitors files
while True:
if file_has_changed:
self.image_signal.emit('/path/to/image/file.jpg')
class MyWidget(QtGui.QWidget):
def __init__(self, ...)
...
self.file_monitor = FileMonitor()
self.thread = QtCore.QThread(self)
self.file_monitor.image_signal.connect(self.image_callback)
self.file_monitor.moveToThread(self.thread)
self.thread.started.connect(self.file_monitor.monitor_images)
self.thread.start()
@QtCore.pyqtSlot(str)
def image_callback(self, filepath):
pixmap = QtGui.QPixmap(filepath)
...