QThread在终止而不是终止后会加速

问题描述 投票:0回答:2

我对QThread的行为完全感到困惑。我的想法是在qthread中获取一些音频信号,将其保存在python queue对象中,并使用QTimer读取队列并使用pyqtgraph进行绘制。但是,它只能以大约6-7 fps的速度工作。但是,当我使用.terminate()终止线程时,该线程实际上并没有终止,而是达到了> 100 fps的速度,这正是我真正想要的。]

我的问题:

  • 为什么QThread不会终止/中止/关闭...?
  • .terminate()实际在做什么?
  • 什么使正常的thread.start()速度变慢?
  • 附带说明,我知道我没有使用信号/插槽检查它是否仍应运行,我只是想了解这种奇怪的行为,以及为什么线程从一开始就不快速!某些东西可能会阻止正常的功能,并且已通过.terminate()功能将其关闭(?!)...

我的最小工作示例(希望你们在某处有声卡/麦克风):

from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QPushButton
from PyQt5.QtCore import QThread, QTimer
import sounddevice as sd
import queue
import pyqtgraph as pg
import numpy as np
import time

class Record(QThread):
    def __init__(self):
        super().__init__()
        self.q = queue.Queue()

    def callback(self, indata, frames, time, status):
        self.q.put(indata.copy())

    def run(self):
        with sd.InputStream(samplerate=48000, device=1, channels=2, callback=self.callback, blocksize=4096):
            print('Stream started...')
            while True:
                pass

        print(self.isRunning(), 'Done?') # never called

class Main(QWidget):
    def __init__(self):
        super().__init__()
        self.recording = False
        self.r = None
        self.x = 0
        self.times = list(range(10))

        self.setWindowTitle("Record Audio Tester")

        self.l = QGridLayout()
        self.setLayout(self.l)

        self.pl = pg.PlotWidget(autoRange=False)
        self.curve1 = self.pl.plot(np.zeros(8000))
        self.curve2 = self.pl.plot(np.zeros(8000)-1, pen=pg.mkPen("y"))

        self.l.addWidget(self.pl)

        self.button_record = QPushButton("Start recording")
        self.button_record.clicked.connect(self.record)
        self.l.addWidget(self.button_record)

    def record(self):
        if self.recording and self.r is not None:
            self.button_record.setText("Start recording")
            self.recording = False
            self.r.terminate()

        else:
            self.button_record.setText("Stop recording")
            self.recording = True

            self.r = Record()
            self.r.start()

            self.t = QTimer()
            self.t.timeout.connect(self.plotData)
            self.t.start(0)

    def plotData(self):
        self.times = self.times[1:]
        self.times.append(time.time())

        fps = 1 / (np.diff(np.array(self.times)).mean() + 1e-5)
        self.setWindowTitle("{:d} fps...".format(int(fps)))

        if self.r.q.empty():
            return

        d = self.r.q.get()

        self.curve1.setData(d[:, 0])
        self.curve2.setData(d[:, 1]-3)


if __name__ == '__main__':
    app = QApplication([])

    w = Main()
    w.show()

    app.exec_()

编辑1

第一个建议@Dennis Jensen是not

子类QThread,但是使用QObject / QThread / moveToThread。我这样做了,请参见下面的代码,并且可以看到使用while以及仅使用app.processEvents()whiletime.sleep(0.1)都解决了问题,但是要使其响应,无论如何都必须使用app.processEvents() ,所以这就足够了。仅使用pass语句会占用大量CPU处理能力,从而导致7-10 fps,但是如果您使用thread.terminate()此线程,则所有内容仍将运行。

我另外添加了一个跟踪,在哪个线程上发生什么,并且回调始终在单独的线程上,无论您使用哪个回调(在QObject或主线程中的任何类之外),都表示来自@three_pineapples的答案是正确的。

from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QPushButton, QCheckBox
from PyQt5.QtCore import QThread, QTimer, QObject, pyqtSignal, pyqtSlot
import threading
import sounddevice as sd
import queue
import pyqtgraph as pg
import numpy as np
import time

q = queue.Queue()

# It does not matter at all where the callback is,
# it is always on its own thread...
def callback(indata, frames, time, status):
        print("callback", threading.get_ident())
        # print()
        q.put(indata.copy())

class Record(QObject):
    start = pyqtSignal(str)
    stop = pyqtSignal()
    data = pyqtSignal(np.ndarray)

    def __init__(self, do_pass=False, use_terminate=False):
        super().__init__()
        self.q = queue.Queue()
        self.r = None
        self.do_pass = do_pass
        self.stop_while = False
        self.use_terminate = use_terminate
        print("QObject -> __init__", threading.get_ident())

    def callback(self, indata, frames, time, status):
        print("QObject -> callback", threading.get_ident())
        self.q.put(indata.copy())

    @pyqtSlot()
    def stopWhileLoop(self):
        self.stop_while = True

    @pyqtSlot()
    def run(self, m='sth'):
        print('QObject -> run', threading.get_ident())

        # Currently uses a callback outside this QObject
        with sd.InputStream(device=1, channels=2, callback=callback) as stream:
            # Test the while pass function
            if self.do_pass:
                while not self.stop_while:
                    if self.use_terminate: # see the effect of thread.terminate()...
                        pass # 7-10 fps
                    else:
                        app.processEvents() # makes it real time, and responsive

                print("Exited while..")
                stream.stop()

            else:
                while not self.stop_while:
                    app.processEvents() # makes it responsive to slots
                    time.sleep(.01) # makes it real time

                stream.stop()

        print('QObject -> run ended. Finally.')

class Main(QWidget):
    def __init__(self):
        super().__init__()
        self.recording = False
        self.r = None
        self.x = 0
        self.times = list(range(10))
        self.q = queue.Queue()

        self.setWindowTitle("Record Audio Tester")

        self.l = QGridLayout()
        self.setLayout(self.l)

        self.pl = pg.PlotWidget(autoRange=False)
        self.curve1 = self.pl.plot(np.zeros(8000))
        self.curve2 = self.pl.plot(np.zeros(8000)-1, pen=pg.mkPen("y"))

        self.l.addWidget(self.pl)

        self.button_record = QPushButton("Start recording")
        self.button_record.clicked.connect(self.record)
        self.l.addWidget(self.button_record)

        self.pass_or_sleep = QCheckBox("While True: pass")
        self.l.addWidget(self.pass_or_sleep)

        self.use_terminate = QCheckBox("Use QThread terminate")
        self.l.addWidget(self.use_terminate)

        print("Main thread", threading.get_ident())

    def streamData(self):
        self.r = sd.InputStream(device=1, channels=2, callback=self.callback)

    def record(self):
        if self.recording and self.r is not None:
            self.button_record.setText("Start recording")
            self.recording = False
            self.r.stop.emit()

            # And this is where the magic happens:
            if self.use_terminate.isChecked():
                self.thr.terminate()

        else:
            self.button_record.setText("Stop recording")
            self.recording = True

            self.t = QTimer()
            self.t.timeout.connect(self.plotData)
            self.t.start(0)

            self.thr = QThread()
            self.thr.start()

            self.r = Record(self.pass_or_sleep.isChecked(), self.use_terminate.isChecked())
            self.r.moveToThread(self.thr)
            self.r.stop.connect(self.r.stopWhileLoop)
            self.r.start.connect(self.r.run)
            self.r.start.emit('go!')

    def addData(self, data):
        # print('got data...')
        self.q.put(data)

    def callback(self, indata, frames, time, status):
        self.q.put(indata.copy())
        print("Main thread -> callback", threading.get_ident())


    def plotData(self):
        self.times = self.times[1:]
        self.times.append(time.time())

        fps = 1 / (np.diff(np.array(self.times)).mean() + 1e-5)
        self.setWindowTitle("{:d} fps...".format(int(fps)))

        if q.empty():
            return

        d = q.get()
        # print("got data ! ...")

        self.curve1.setData(d[:, 0])
        self.curve2.setData(d[:, 1]-1)


if __name__ == '__main__':
    app = QApplication([])

    w = Main()
    w.show()

    app.exec_()

编辑2

这里的代码不使用QThread环境,因此可以正常工作!

from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QPushButton, QCheckBox
from PyQt5.QtCore import QTimer
import threading
import sounddevice as sd
import queue
import pyqtgraph as pg
import numpy as np
import time


class Main(QWidget):
    def __init__(self):
        super().__init__()
        self.recording = False
        self.r = None
        self.x = 0
        self.times = list(range(10))
        self.q = queue.Queue()

        self.setWindowTitle("Record Audio Tester")

        self.l = QGridLayout()
        self.setLayout(self.l)

        self.pl = pg.PlotWidget(autoRange=False)
        self.curve1 = self.pl.plot(np.zeros(8000))
        self.curve2 = self.pl.plot(np.zeros(8000)-1, pen=pg.mkPen("y"))

        self.l.addWidget(self.pl)

        self.button_record = QPushButton("Start recording")
        self.button_record.clicked.connect(self.record)
        self.l.addWidget(self.button_record)

        print("Main thread", threading.get_ident())

    def streamData(self):
        self.r = sd.InputStream(device=1, channels=2, callback=self.callback)
        self.r.start()

    def record(self):
        if self.recording and self.r is not None:
            self.button_record.setText("Start recording")
            self.recording = False
            self.r.stop()

        else:
            self.button_record.setText("Stop recording")
            self.recording = True

            self.t = QTimer()
            self.t.timeout.connect(self.plotData)
            self.t.start(0)

            self.streamData()

    def callback(self, indata, frames, time, status):
        self.q.put(indata.copy())
        print("Main thread -> callback", threading.get_ident())


    def plotData(self):
        self.times = self.times[1:]
        self.times.append(time.time())

        fps = 1 / (np.diff(np.array(self.times)).mean() + 1e-5)
        self.setWindowTitle("{:d} fps...".format(int(fps)))

        if self.q.empty():
            return

        d = self.q.get()
        # print("got data ! ...")

        self.curve1.setData(d[:, 0])
        self.curve2.setData(d[:, 1]-1)


if __name__ == '__main__':
    app = QApplication([])

    w = Main()
    w.show()

    app.exec_()

我对QThread的行为完全感到困惑。我的想法是在qthread中获取一些音频信号,将其保存在python队列对象中,并使用QTimer读取队列并使用pyqtgraph对其进行绘制。它...

python asynchronous audio pyqt5 qthread
2个回答
2
投票

0
投票
while
© www.soinside.com 2019 - 2024. All rights reserved.