web-dev-qa-db-ja.com

PyQTスレッディングの最も簡単な方法

PyQtに関数addImage(image_path)のGUIがあります。想像しやすいですが、新しい画像をQListWidgetに追加する必要があるときに呼び出されます。フォルダー内の新しい画像の検出には、threading.Thread with watchdogを使用してフォルダー内のファイルの変更を検出し、このスレッドがaddImageを直接呼び出します。

これにより、スレッドの安全性の理由から、QPixmapをGUIスレッドの外部で呼び出すべきではないという警告が表示されます。

このスレッドセーフにするための最良かつ最も簡単な方法は何ですか? QThread?シグナル/スロット? QMetaObject.invokeMethod?スレッドからaddImageに文字列を渡すだけです。

8
user3696412

最善の方法は、信号/スロットメカニズムを使用することです。ここに例があります。 (注:以下の[〜#〜] 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とのスレッドセーフな通信のいくつかのアプローチを比較するようにアドバイスしました。私はその問題を掘り下げ、明日それをします:-)

10
K.Mulier

Qtが提供する組み込みのQThreadを使用する必要があります。 QObjectから継承するworkerクラス内にファイル監視コードを配置して、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)
        ...
12
Brendan Abel