Skip to main content
 首页 » 编程设计

python之PyQt4:如何避免这种竞争条件 QThread 在完全启动之前断开连接

2025年05月04日22rubylouvre

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

我遇到了启动 QThread 然后立即断开连接导致运行时出现奇怪行为的问题。在我发现“竞争条件”是什么意思(或者真正了解多线程)之前,我暗暗怀疑线程在断开连接之前没有完全启动。为了克服这个问题,我在 start() 和 disconnect() 调用之间添加了 5 毫秒的 sleep 时间,效果非常好。它就像一个魅力,但它不是正确的方法。

如何在不调用 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() 

请您参考如下方法:

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

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

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

If the thread is already running, this function does nothing.

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