2016-05-16 99 views
3

我在PyQt中有一个功能addImage(image_path)的GUI。很容易想象,当一个新图像应该添加到QListWidget中时,它会被调用。为了检测文件夹中的新图像,我使用threading.Threadwatchdog来检测文件夹中的文件更改,然后该线程直接调用addImagePyQT线程最简单的方法

这就产生了警告,QPixmap不应该被称为GUI线程外,线程安全的原因。

什么是最好和最简单的方法来使这个threadsafe? QThread的?信号/插槽? QMetaObject.invokeMethod?我只需要从线程传递一个字符串到addImage

+0

您可以发布一些代码,以便我们可以看到错误/警告被触发的位置吗? –

回答

7

我相信最好的方法是使用信号/插槽机制。这是一个例子。 (注意:请参阅下面的编辑,指出了我的方法可能存在的一个弱点)。

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亚伯先生指出,在我的方法的一个弱点。事实上,这种方法适用于这种特殊情况,因为您直接生成/发出信号。当您处理按钮和小部件上的内置Qt信号时,您应该采取另一种方法(正如Brendan Abel先生的回答中所述)。

three_pineapples先生劝我开始一个新的话题在StackOverflow上使线程安全通信的几种方法与GUI之间的比较。我将深入调查此事,并做明天:-)

+0

工程就像一个魅力,非常感谢! – user3696412

+0

不客气。我很高兴它帮助你:-) –

+0

我今天遇到了这个问题。我也是,我以线程不安全的方式访问了我的GUI。我的应用程序崩溃惨,如果没有适当的错误报告: http://stackoverflow.com/questions/37242849/python-program-crashes-because-of-ntdll-dll-and-qtgui4-dll 然后我用了信号/槽机制,正如我在答复中所述。它帮助我。我很高兴它可以帮助你:-) –

9

你应该使用内置的Qt提供QThread。您可以将文件监视代码放在工作人员类中,该类继承自QObject,以便它可以使用Qt信号/插槽系统在线程之间传递消息。

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) 
     ... 
+0

这是不是基本上与上述答案相同,只是使用'QThread'而不是'threading.Thread'?似乎是几乎相同的代码。 – user3696412

+0

区别在于,这是在Qt/PyQt中使用线程的规范方式 - 我的示例与* Worker Model * [在官方文档中描述]基本相同(http://doc.qt.io/qt -4.8/qthread.html)。通常也不鼓励从这些对象之外发送其他对象上的信号,而其他答案所做的以及官方工作者模型所不具备的信号。 –

+0

嗨Brendan @Brendan Abel, 您能否澄清一下:“通常也不鼓励从这些物体外发射其他物体上的信号,而其他答案所做的以及官方工作人员模型所没有的信号”?我很想知道更多关于此的信息。因为我在最近的应用程序中使用了信号/插槽机制。非常感谢:-) –