将线程传递给线程对象

时间:2014-06-03 11:23:29

标签: python multithreading pyqt4 qthread

关于在PyQt4和Python 2.7中使用QThread的快速问题。我正在创建一个继承自QObject的流程,并将其分配给我在一个单独的类中创建的Qthread(也继承自QObject)。

QThread对象传递给进程对象是否安全,以便我可以在进程内调用thread.msleep(mseconds)

我希望能够使线程等待或休眠,但我已经读过,当与PyQt多线程一起使用时,time.sleep(秒)是狡猾的。

我确实尝试将进程对象的信号发送到主线程中的一个插槽(附加到该进程对象的thread.msleep(mseconds)),但我发现这无法工作;进程对象继续执行直到完成,此时后才执行插槽。即使在调整优先级之后,这仍然会发生。这是不可接受的,因为我希望流程循环能够连续运行。

还有其他建议吗?

1 个答案:

答案 0 :(得分:1)

我最终设法改变我的代码以实现我在我的问题中所需的功能:即使线程在指定的时间内等待或休眠的能力。

首先,我的研究似乎表明,Qt中子类化QThread变得不明智的一个主要原因是线程不应该能够管理自己。虽然我的问题没有官方文档,但我只能推测将线程对象传递给在其上运行的进程对象也是不明智的,因为线程将再次能够直接控制自己。

我找到的解决方案是完全免除msleep()。关于QThread的Qt文档建议避免使用sleep()wait()函数,因为它们不适合Qt的事件驱动特性。他们建议QTimer()用于在超时后通过信号调用函数,代替msleep()。默认情况下,QTimer()用于在每个时间间隔发送重复信号,但也可以使用QTimer.singleShot()发送一次信号。文档中还指出,在一个帖子中调用QSleep()是安全的。

我只使用重复QTimer多次调用单个广告位foo(),但要在foo()内添加延迟,QTimer.singleShot()可用于拨打秒在设定的毫秒数后函数moo()

编辑:我已经决定包含我的线程代码,它将QObject和QThread子类化,以便在每个给定时间间隔的连续循环中对线程执行任务。据我所知,这是完全可行的,但可以做一点工作。

# -*- 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_() )