【问题标题】:Simplest way for PyQT ThreadingPyQT 线程的最简单方法
【发布时间】:2016-09-12 04:11:03
【问题描述】:

我在 PyQt 中有一个带有函数 addImage(image_path) 的 GUI。很容易想象,当一个新的图像应该被添加到一个 QListWidget 时调用它。对于文件夹中新图像的检测,我使用threading.Threadwatchdog来检测文件夹中的文件更改,然后该线程直接调用addImage

这会产生警告:出于线程安全的原因,不应在 gui 线程之外调用 QPixmap

使这个线程安全的最好和最简单的方法是什么?问线程?信号/插槽? QMetaObject.invokeMethod?我只需要将线程中的字符串传递给addImage

【问题讨论】:

  • 您能否发布一些代码,以便我们查看触发错误/警告的位置?

标签: python qt pyqt thread-safety python-watchdog


【解决方案1】:

我认为最好的方法是使用信号/槽机制。这是一个例子。 (注意:请参阅下面的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 建议我在 StackOverflow 中开始一个新主题,以比较使用 GUI 进行线程安全通信的几种方法。我会深入研究这件事,明天再做:-)

【讨论】:

  • 我今天遇到了这个问题。我也是,我以线程不安全的方式访问了我的 GUI。我的应用程序崩溃得很惨,没有正确的错误报告:stackoverflow.com/questions/37242849/… 然后我使用了我在回答中描述的信号/插槽机制。它帮助了我。我很高兴它对你也有帮助:-)
  • 谁对这个答案投了反对票,却没有发表评论? :-( 在投反对票之前,请发表评论以解释为什么这个答案不好。
  • 我是个通情达理的人。任何觉得我的回答不好的人都可以告诉我原因,我会尽力做出适当的改变。但是,只是在没有任何解释的情况下投票对任何人都没有帮助。请..
  • @K.Mulier 我不是投反对票的人,但我怀疑投反对票是因为这违背了this 问题的共识,即使用Qt 线程。虽然它目前适用于 Python 线程(我个人曾多次使用 Python 线程做过类似的事情,没有任何问题),但不能保证它不会与 PyQt/Python 的未来版本中断。
  • 谢谢先生。三菠萝。你的评论真的很有趣。我将按照您的建议在 StackOverflow 上开始一个关于该主题的新主题。谢谢你的帮助:-)
【解决方案2】:

您应该使用 Qt 提供的内置 QThread。您可以将文件监控代码放在继承自 QObjectworker 类中,以便它可以使用 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)
        ...

【讨论】:

  • 这不是和上面的答案基本一样,只是用QThread而不是threading.Thread?似乎是几乎相同的代码。
  • 不同之处在于这是在 Qt/PyQt 中使用线程的规范方式——我的示例与 Worker 模型 described in the official documentation 基本相同。通常也不鼓励从这些对象之外的其他对象上发出信号,其他答案会这样做,而官方工作模型则不会。
  • 嗨,Brendan @Brendan Abel,您能否澄清一下:“通常也不鼓励从这些对象外部向其他对象发出信号,而其他答案会这样做,而官方工人模型不会”?我有兴趣了解更多关于这方面的信息。因为我在最近的应用程序中经常使用信号/槽机制。非常感谢:-)
  • 在内部,Qt 信号被实现为受保护的成员函数。因此,虽然您所做的可能在此示例中有效(因为信号是在 python 中创建的),但它不能作为通用解决方案(即它通常不适用于按钮和小部件上的内置 Qt 信号)。
  • 什么是“受保护的成员函数”?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-22
  • 1970-01-01
  • 2012-04-26
  • 1970-01-01
  • 1970-01-01
  • 2013-03-29
  • 2020-08-07
相关资源
最近更新 更多