Thread an Thread-Objekt übergeben
-
21-12-2019 - |
Frage
Kurze Frage zur Verwendung von QThread
in PyQt4 und Python 2.7.Ich erstelle einen Prozess, der von geerbt wurde QObject
, und dies einem zuweisen Qthread
Ich habe in einer separaten Klasse erstellt (auch geerbt von QObject
).
Ist es sicher, das zu bestehen QThread
objekt zum Prozessobjekt, damit ich es aufrufen kann thread.msleep(mseconds)
innerhalb des Prozesses selbst?
Ich möchte den Thread warten oder schlafen lassen können, aber ich habe diese Zeit gelesen.schlaf (Sekunden) ist bei Verwendung mit PyQt-Multithreading zweifelhaft.
Ich habe versucht, ein Signal vom Prozessobjekt an einen Steckplatz im Hauptthread zu senden (angehängt an thread.msleep(mseconds)
für dieses Prozessobjekt), aber ich habe festgestellt, dass dies nicht funktioniert hat;das Prozessobjekt wurde bis zum Abschluss weiter ausgeführt, wobei der Slot erst nach dieser Zeit ausgeführt wurde.Auch nach einer Anpassung der Prioritäten geschah dies weiterhin.Dies ist inakzeptabel, da ich möchte, dass die Prozessschleife kontinuierlich ausgeführt wird.
Irgendwelche anderen Empfehlungen?
Lösung
Es gelang mir schließlich, meinen Code zu ändern, um die Funktionalität zu erreichen, die ich in meiner Frage benötigte:nämlich die Fähigkeit, einen Thread für eine bestimmte Zeit warten oder schlafen zu lassen.
Erstens scheint meine Forschung zu zeigen, dass einer der Hauptgründe für die Unterklasse QThread
in Qt wurde schlecht beraten, dass ein Thread sich nicht selbst verwalten kann.Obwohl es zu meiner Frage keine offizielle Dokumentation gibt, kann ich nur vermuten, dass die Übergabe des Thread-Objekts an das darauf ausgeführte Prozessobjekt ebenfalls nicht ratsam wäre, da sich der Thread wieder direkt selbst steuern könnte.
Die Lösung, die ich gefunden habe, ist zu verzichten msleep()
insgesamt.Die Qt-Dokumentation zu QThread empfiehlt dies sleep()
und wait()
funktionen werden vermieden, weil sie nicht gut zur ereignisgesteuerten Natur von Qt passen.Sie empfehlen das QTimer()
wird verwendet, um eine Funktion über ein Signal aufzurufen, nachdem sie abgelaufen ist, anstelle von msleep()
.Standardmäßig QTimer()
wird verwendet, um jedes Zeitintervall ein sich wiederholendes Signal zu senden, kann aber auch einmal ein Signal senden mit QTimer.singleShot()
.In der Dokumentation wird auch angegeben, dass es sicher ist, anzurufen QSleep()
innerhalb eines Threads.
Ich benutze nur eine Wiederholung QTimer
so rufen Sie einen einzelnen Slot auf foo()
mehrmals, aber um eine Verzögerung innerhalb hinzuzufügen foo()
, QTimer.singleShot()
könnte verwendet werden, um eine zweite Funktion aufzurufen moo()
nach einer festgelegten Anzahl von Millisekunden.
BEARBEITEN:Ich habe beschlossen, meinen Threading-Code einzuschließen, der QObject und QThread Unterklassen QObject , um in jedem bestimmten Zeitintervall eine Aufgabe für einen Thread in einer kontinuierlichen Schleife auszuführen.Es ist, soweit ich das beurteilen kann, voll funktionsfähig, könnte aber mit ein wenig Arbeit gebrauchen.
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
# Class to be assigned to a thread.
# This should be subclassed to provide new functionality.
class GenericLoop(QtCore.QObject):
def __init__(self):
super(GenericLoop, self).__init__()
# We use this signal to tell the main thread
# when this thread is finished.
finished_Sig = QtCore.pyqtSignal()
# Default timeout is 0, i.e. do work on thread after
# other events have been dealt with
__timeout = 0
__processTimer = None
__args = None
__kwargs = None
# We use this function to set the arguments used by run(),
# if we want to change them mid-execution
@QtCore.pyqtSlot(tuple, dict)
def changeArgs(self, args, kwargs):
self.__args = args
self.__kwargs = kwargs
# We can change the timeout used to make the thread run
# at given intervals. Note that the timing is not exact,
# since this is impossible with a real time operating system
@QtCore.pyqtSlot(int)
def setTimeout(self, mseconds):
self.__timeout = int(mseconds)
# Call either a singleShot QTimer (one execution),
# or a normal QTimer (repeated), to start the loop
@QtCore.pyqtSlot(bool, tuple, dict)
def startTimer(self, singleShot, args, kwargs):
self.__processTimer = QtCore.QTimer()
# We can't pass args and kwargs directly because QTimer.timeout
# emits a signal with allowing no contained variables
# so we copy args and kwargs to local variables instead
self.changeArgs(args, kwargs)
if singleShot:
self.__processTimer.singleShot(self.__timeout, self.callRun)
else:
self.__processTimer.timeout.connect(self.callRun)
self.__processTimer.start(self.__timeout)
# Call finish from within subclass using self.finish(), or
# from another thread using signals. finish() will stop the
# QTimer causing execution of the loop. The loop can be started again
# by calling startTimer() or stopTimer() from another thread
@QtCore.pyqtSlot()
def stopTimer(self):
if self.__processTimer.isActive():
self.__processTimer.stop()
else:
print "ERROR: stopTimer() has been called but no timer is running!"
# We call this to delete the thread.
@QtCore.pyqtSlot()
def deleteThread(self):
self.finished_Sig.emit()
# This calls run(), in order to enable the passing of
# command line arguments to the loop
@QtCore.pyqtSlot()
def callRun(self):
self.run(self.__args, self.__kwargs)
# run() can be called directly from another thread if required
@QtCore.pyqtSlot(tuple, dict)
def run(self, args, kwargs):
print "ERROR: run() has not been defined! Stopping thread..."
self.stopTimer()
# Class for creating threads
class GenericThread(QtCore.QObject):
# Private variables include the thread.
__sendArguments_Sig = QtCore.pyqtSignal(tuple, dict)
__startTimer_Sig = QtCore.pyqtSignal(int, tuple, dict)
__setTimeout_Sig = QtCore.pyqtSignal(int)
__obj = None
__finished_Sig = None
__thread = QtCore.QThread()
# Object to be threaded must be specified when
# creating a GenericThread object
def __init__(self, obj):
super(GenericThread, self).__init__()
self.__obj = obj
self.moreInit()
# Set up object on thread
def moreInit(self):
self.__thread = QtCore.QThread()
self.__obj.moveToThread(self.__thread)
# Allows thread to delete itself when done
self.__obj.finished_Sig.connect(self.__thread.deleteLater)
self.__sendArguments_Sig.connect(self.__obj.changeArgs)
self.__startTimer_Sig.connect(self.__obj.startTimer)
self.__setTimeout_Sig.connect(self.__obj.setTimeout)
self.__thread.start()
# Sets the QTimer timeout and does some checking
# to make sure that types are as they should be
def setTimeout(self, mseconds):
if mseconds >= 0 and type(mseconds) is type(int()):
self.__setTimeout_Sig.emit(mseconds)
elif mseconds < 0 and type(mseconds) is type(int()):
print "Error: timeout of below 0 ms specified."
else:
print "Error: timeout period is specified with a type other than int."
# Starts a function in the thread via signals, and can pass
# it arguments if required. Function executes until QTimer is stopped
def startLoop(self, *args, **kwargs):
if (self.__thread == None):
print "ERROR: Thread has been deleted!"
else:
self.__startTimer_Sig.emit(False, args, kwargs)
# Starts a function in the thread via signals, once
def startOnce(self, *args, **kwargs):
if (self.__thread == None):
print "ERROR: Thread has been deleted!"
else:
self.__startTimer_Sig.emit(True, args, kwargs)
# Calls a very simple GUI just to show that the program is responsive
class GUIBox(QtGui.QWidget):
def __init__(self):
super(GUIBox, self).__init__()
self.initUI()
def initUI(self):
self.resize(250, 150)
self.setWindowTitle('Threading!')
self.show()
# Subclass GenericLoop to reimplement run and such.
class SubClassedLoop(GenericLoop):
def __init__(self):
super(SubClassedLoop, self).__init__()
__i = 0
@QtCore.pyqtSlot(tuple, dict)
def run(self, args, kwargs):
if self.__i>=50:
self.stopTimer()
return
print self.__i, args
self.__i += 1
app = QtGui.QApplication(sys.argv)
ex = GUIBox()
# Create 3 worker objects to do the actual calculation
worker1 = SubClassedLoop()
worker2 = SubClassedLoop()
worker3 = SubClassedLoop()
# Create 3 thread managing objects to do the thread control
thread1 = GenericThread(worker1)
thread2 = GenericThread(worker2)
thread3 = GenericThread(worker3)
# Set the threads to execute as soon as there is no work to do
thread1.setTimeout(125)
thread2.setTimeout(125)
thread3.setTimeout(125)
# Start threads
thread1.startLoop(1)
thread2.startLoop(2)
thread3.startLoop(3)
# Quit the program when the GUI window is closed
sys.exit( app.exec_() )