如何避免 PyQt4 中的此竞争状况?在完全启动之前断开 QThread

31 阅读2分钟

在 PyQt4 中,有一项目标是使用单个 QThread 在不同时间运行两个(或更多)单独的方法。例如,希望 QThread 有时运行 play(),并在播放完成后,希望将 QThread 从此方法断开连接,以便将其连接到其他位置。本质上,希望 QThread 充当容器,用于任何希望与主进程并行运行的内容。

huake_00198_.jpg 在启动 QThread 然后立即断开其连接的情况下遇到了问题,这会导致运行时出现奇怪的行为。在了解“竞争条件”的含义(或真正了解有关多线程的很多知识)之前,怀疑线程在断开连接之前尚未完全启动。为了克服这个问题,在 start() 和 disconnect() 调用之间添加了 5 毫秒的睡眠,并且它工作得很好。它工作得很好,但这不是正确的方式。

解决方案

可以使用以下方法来实现此功能,而无需调用 sleep():

  1. 在将 MouseRecord 对象移动到 QThread 之前,将 started 信号连接到 record() 方法。
  2. 在 QThread 开始后断开连接 started 信号。
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()

        # 将 started 信号连接到 record() 方法
        self.myThread.started.connect(self.mouseRecorder.record)

        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.start()

        # 在 QThread 启动后断开 started 信号的连接
        self.myThread.started.disconnect()

    def play(self):

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

        self.myThread.start()

        # 在 QThread 启动后断开 started 信号的连接
        self.myThread.started.disconnect()

通过这种方法,现在可以将 MouseRecord 对象移动到 QThread,并在 QThread 开始后断开 started 信号的连接,而无需使用 5 毫秒的睡眠。这将防止竞争条件并确保在断开连接之前完全启动 QThread。