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