使用 QThread 时启动 QTimer 的问题

Problems getting QTimer to start when using QThread

我正在尝试使用我发现的示例中的 PyQt5 实现网络摄像头(,但不是很相关)。 让示例运行不是问题,但我想修改一些东西,但我遇到了一个特定问题。

我有两个 类,一个 QObject Capture 有一个我想启动的 QBasicTimer,还有一个 QWidget MyWidget 带有一个应该启动计时器的按钮Capture 对象,在 QThread 中。

如果我直接将按钮点击连接到启动计时器的方法,一切正常。

但是我想在点击按钮的时候做一些其他的事情,所以我先把按钮连接到MyWidget的一个方法上,然后调用Capturestart方法那里。但是,这不起作用:计时器没有启动。

这是一个最小的工作示例:

from PyQt5 import QtCore, QtWidgets
import sys

class Capture(QtCore.QObject):
    def __init__(self, parent=None):
        super(Capture, self).__init__(parent)

        self.m_timer = QtCore.QBasicTimer()

    def start(self):
        print("capture start called")
        self.m_timer.start(1000, self)

    def timerEvent(self, event):
        print("time event")


class MyWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)    

        lay = QtWidgets.QVBoxLayout(self)
        self.btn_start = QtWidgets.QPushButton("Start")
        lay.addWidget(self.btn_start)        

        self.capture = Capture()
        captureThread = QtCore.QThread(self)
        captureThread.start()
        self.capture.moveToThread(captureThread)
        
        # self.btn_start.clicked.connect(self.capture.start) # this works
        self.btn_start.clicked.connect(self.startCapture) # this doesn't
        # self.capture.start() # this doesn't either
        
        self.show()
        
    def startCapture(self):
        self.capture.start()


def run_app():
    app = QtWidgets.QApplication(sys.argv)
    mainWin = MyWidget()
    mainWin.show()
    app.exec_()

run_app()

这是 QThread 的一些问题,因为如果我不使用线程它就可以工作。我认为这可能与线程在从创建它的方法不同的方法调用时无法以某种方式可用有关,但是直接从 init 调用 self.capture.start() 也不起作用.

我对线程只有很基础的了解。有人能告诉我如何从 MyWidget 正确调用 self.capture.start() 以及为什么直接将它连接到按钮点击时它可以正常工作吗?

如果您将按钮的 clicked 信号连接到工人的 start 插槽,Qt 将自动检测到它是一个 cross-thread 连接。当信号最终发出时,它将在接收线程的 event-queue 中排队,这确保将在工作线程中调用插槽。

但是,如果将按钮的 clicked 信号连接到 startCapture 插槽,则没有 cross-thread 连接,因为插槽属于 MyWidget(位于主线程)。这次发出信号时,插槽会尝试从主线程中创建计时器,这是不支持的。计时器必须始终在创建它们的线程内启动(否则 Qt 将打印一条消息,如“QBasicTimer::start:计时器无法从另一个线程启动”)。

更好的做法是将线程的startedfinished信号连接到worker中的一些startstop槽,然后调用线程的startquit 方法来控制工人。这是一个基于您的脚本的演示,展示了如何实现它:

from PyQt5 import QtCore, QtWidgets
import sys

class Capture(QtCore.QObject):
    def __init__(self, parent=None):
        super(Capture, self).__init__(parent)
        self.m_timer = QtCore.QBasicTimer()

    def start(self):
        print("capture start called")
        self.m_timer.start(1000, self)

    def stop(self):
        print("capture stop called")
        self.m_timer.stop()

    def timerEvent(self, event):
        print("time event")

class MyWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        lay = QtWidgets.QVBoxLayout(self)
        self.btn_start = QtWidgets.QPushButton("Start")
        lay.addWidget(self.btn_start)
        self.capture = Capture()
        self.captureThread = QtCore.QThread(self)
        self.capture.moveToThread(self.captureThread)
        self.captureThread.started.connect(self.capture.start)
        self.captureThread.finished.connect(self.capture.stop)
        self.btn_start.clicked.connect(self.startCapture)
        self.show()

    def startCapture(self):
        if not self.captureThread.isRunning():
            self.btn_start.setText('Stop')
            self.captureThread.start()
        else:
            self.btn_start.setText('Start')
            self.stopCapture()

    def stopCapture(self):
        self.captureThread.quit()
        self.captureThread.wait()

    def closeEvent(self, event):
        self.stopCapture()

def run_app():
    app = QtWidgets.QApplication(sys.argv)
    mainWin = MyWidget()
    mainWin.show()
    app.exec_()

run_app()