python - La forma más sencilla para subprocesos PyQT
thread-safety python-watchdog (2)
Tengo una GUI en PyQt con una función
addImage(image_path)
.
Fácil de imaginar, se llama cuando se debe agregar una nueva imagen a un QListWidget.
Para la detección de nuevas imágenes en una carpeta, utilizo un
threading.Thread
Hilo con
watchdog
para detectar cambios en el archivo en la carpeta, y este hilo llama a
addImage
directamente.
Esto genera la advertencia de que
QPixmap
no debe llamarse fuera del hilo de la interfaz
QPixmap
, por razones de seguridad del hilo.
¿Cuál es la mejor y más simple forma de hacer que este hilo sea seguro?
QThread?
Señal / Ranura?
QMetaObject.invokeMethod?
Solo necesito pasar una cadena desde el hilo para
addImage
.
Creo que el mejor enfoque es usar el mecanismo de señal / ranura. Aquí hay un ejemplo. (Nota: vea el EDITAR a continuación que señala una posible debilidad en mi enfoque).
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 ''''''
En su código de aplicación GUI, debe crear el nuevo hilo, darle la función de devolución de llamada correcta y hacer que se ejecute.
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 ''''''
EDITAR
Mr. three_pineapples y Mr. Brendan Abel señalaron una debilidad en mi enfoque. De hecho, el enfoque funciona bien para este caso particular, porque usted genera / emite la señal directamente. Cuando maneja señales Qt incorporadas en botones y widgets, debe adoptar otro enfoque (como se especifica en la respuesta del Sr. Brendan Abel).
El Sr. three_pineapples me aconsejó que comenzara un nuevo tema en para hacer una comparación entre los diversos enfoques de comunicación segura para subprocesos con una GUI. Profundizaré en el asunto y lo haré mañana :-)
Debe usar el
QThread
proporcionado por Qt.
Puede colocar su código de monitoreo de archivos dentro de una clase de
trabajo
que hereda de
QObject
para que pueda usar el sistema Qt Signal / Slot para pasar mensajes entre hilos.
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)
...