【问题标题】:PyQt4: How can I avoid this race condition? QThread disconnected before fully startedPyQt4:我怎样才能避免这种竞争条件? QThread 在完全启动之前断开连接
【发布时间】:2013-01-19 21:36:49
【问题描述】:

我有一种情况,我想使用单个 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()

【问题讨论】:

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


    【解决方案1】:

    您想立即启动线程并使用来自 gui 线程的信号和插槽与 MouseRecorder 实例通信。

    您通过启动QThread(您已连接信号以触发特定事件)向MouseRecorder 实例发出信号。通常,如果您有一些事情只需要在工作线程中发生一次,您将希望使用该 sig/slot 连接。否则,您通常会与任何获得moveToThread 信号和槽的QObjects 进行跨线程通信。


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

    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() 的额外需求。

    【讨论】:

      【解决方案2】:

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

      1) 你运行play,然后断开插槽

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

      如果线程已经在运行,这个函数什么也不做。

      (来自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()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-08
        相关资源
        最近更新 更多