PyQt4:我怎样才能避免这种竞争条件? QThread在完全启动之前断开连接

时间:2013-02-05 02:51:44

标签: python multithreading python-2.7 pyqt4 race-condition

我有一种情况,我想使用单个QThread在不同的时间运行两个(或更多)单独的方法。例如,我希望QThread有时会运行play(),当我完成播放时,我想断开QThread与此方法的连接,以便我可以将其连接到其他地方。从本质上讲,我希望QThread能够作为我希望与主流程并行运行的任何东西的容器。

我遇到了启动QThread然后立即断开连接的问题,导致运行时出现奇怪的行为。在我发现“竞争条件”意味着什么(或者真正了解多线程)之前,我有一种潜在的怀疑,即线程在断开之前没有完全启动。为了解决这个问题,我在start()disconnect()来电之间添加了5毫秒的睡眠,它就像一个魅力。它就像一个魅力,但它不是正确的方式。

如何在不调用sleep()的情况下使用一个QThread实现此功能?

问题中的代码段:

def play(self):

        self.stateLabel.setText("Status: Playback initated ...")

        self.myThread.started.connect(self.mouseRecorder.play)
        self.myThread.start()
        time.sleep(.005)  #This is the line I'd like to eliminate

        self.myThread.started.disconnect()

完整脚本:

class MouseRecord(QtCore.QObject):

    finished = QtCore.pyqtSignal()    

    def __init__(self):

        super(MouseRecord, self).__init__()        

        self.isRecording = False
        self.cursorPath = []

    @QtCore.pyqtSlot()  
    def record(self):

        self.isRecording = True
        self.cursorPath = []

        while(self.isRecording):

            self.cursorPath.append(win32api.GetCursorPos())
            time.sleep(.02)            

        self.finished.emit()

    def stop(self):

        self.isRecording = False

    @QtCore.pyqtSlot()    
    def play(self):

        for pos in self.cursorPath:
            win32api.SetCursorPos(pos)
            time.sleep(.02)        

        print "Playback complete!"
        self.finished.emit()            

class CursorCapture(QtGui.QWidget):

    def __init__(self):

        super(CursorCapture, self).__init__()

        self.mouseRecorder = MouseRecord()

        self.myThread = QtCore.QThread()

        self.mouseRecorder.moveToThread(self.myThread)
        self.mouseRecorder.finished.connect(self.myThread.quit)

        self.initUI()

    def initUI(self):

        self.recordBtn = QtGui.QPushButton("Record")
        self.stopBtn   = QtGui.QPushButton("Stop")
        self.playBtn   = QtGui.QPushButton("Play")        

        self.recordBtn.clicked.connect(self.record)
        self.stopBtn.clicked.connect(self.stop)
        self.playBtn.clicked.connect(self.play)

        self.stateLabel = QtGui.QLabel("Status: Stopped.")

        #Bunch of other GUI initialization ...

    def record(self):

        self.stateLabel.setText("Status: Recording ...")  

        self.myThread.started.connect(self.mouseRecorder.record)
        self.myThread.start()
        time.sleep(.005)        

        self.myThread.started.disconnect()

    def play(self):

        self.stateLabel.setText("Status: Playback initated ...")

        self.myThread.started.connect(self.mouseRecorder.play)
        self.myThread.start()
        time.sleep(.005)

        self.myThread.started.disconnect()

2 个答案:

答案 0 :(得分:0)

正确的方法是为每个操作创建新的QThread,这样sleep并且不需要断开连接。现在,即使您成功消除sleep调用,也可能出现以下情况:

1)您运行play,并断开插槽

2)您在record完成之前运行play。在这种情况下,先前创建的线程仍在运行并且:

  

如果线程已在运行,则此函数不执行任何操作。

(来自documentation

如果你接受这种情况并且在某种程度上防止同时“播放”和“录制”,那么你应该像你自己写的那样:“当我完成游戏时,我想断开连接”。所以,不仅仅是在线程启动之后,而是在它完成之后。要做到这一点,试试这个:

1)将self.mouseRecorder.finished.connect(self.myThread.quit)更改为self.mouseRecorder.finished.connect(self.threadFinished)

2)实施:

def threadFinished(self):
        self.myThread.quit()
        self.myThread.started.disconnect()

答案 1 :(得分:0)

您希望立即启动该线程并使用gui线程中的信号和插槽与MouseRecorder实例进行通信。

您通过启动MouseRecorder(您已连接信号以触发特定事件)来发信号通知QThread实例。通常,如果您需要在工作线程中仅发生ONCE,则需要使用该sig / slot连接。否则,您通常会与任何带有moveToThread信号和插槽的QObject进行跨线程通信。


相反,我会把它写成如下:

class MouseRecord(QtCore.QObject):

    def __init__(self):
        super(MouseRecord, self).__init__()        
        self.isRecording = False
        self.cursorPath = []

    @QtCore.pyqtSlot()  
    def record(self):
        self.isRecording = True
        self.cursorPath = []

        while(self.isRecording):
            #Needed, so that if a sigStop is emitted, self.isRecording will be able to be changed
            QApplication.processEvents()

            self.cursorPath.append(win32api.GetCursorPos())
            time.sleep(.02)   

    @QtCore.pyqtSlot()
    def stop(self):
        self.isRecording = False

    @QtCore.pyqtSlot()    
    def play(self):
        for pos in self.cursorPath:
            win32api.SetCursorPos(pos)
            time.sleep(.02)        
        print "Playback complete!"

class CursorCapture(QtGui.QWidget):

    sigRecord = QtCore.pyqtSignal()
    sigPlay = QtCore.pyqtSignal()
    sigStop = QtCore.pyqtSignal()

    def __init__(self):
        super(CursorCapture, self).__init__()

        self.mouseRecorder = MouseRecord()
        self.myThread = QtCore.QThread()

        self.mouseRecorder.moveToThread(self.myThread)

        self.sigRecord.connect(self.mouseRecorder.record)
        self.sigPlay.connect(self.mouseRecorder.play)
        self.sigStop.connect(self.mouseRecorder.stop)

        self.myThread.start()

        self.initUI()

    def initUI(self):
        self.recordBtn = QtGui.QPushButton("Record")
        self.stopBtn   = QtGui.QPushButton("Stop")
        self.playBtn   = QtGui.QPushButton("Play")        

        self.recordBtn.clicked.connect(self.record)
        self.stopBtn.clicked.connect(self.stop)
        self.playBtn.clicked.connect(self.play)

        self.stateLabel = QtGui.QLabel("Status: Stopped.")

        #Bunch of other GUI initialization ...

    def record(self):
        self.stateLabel.setText("Status: Recording ...")  
        self.sigRecord.emit()

    def play(self):
        self.stateLabel.setText("Status: Playback initated ...")
        self.sigPlay.emit()

    def stop(self):
        self.stateLabel.setText("Status: Recording Stopped...")
        self.sigStop.emit()

这将允许QThread始终在运行(这不是问题,因为它没有做任何事情,除非你告诉它),你的MouseRecorder实例正在等待gui线程的信号。

请注意QApplication::processEvents()

的额外需求