如何杀死 PyQt5 中的 QRunnable?
Posted
技术标签:
【中文标题】如何杀死 PyQt5 中的 QRunnable?【英文标题】:How to kill the QRunnable in PyQt5? 【发布时间】:2018-11-28 08:46:20 【问题描述】:我有一个带有两个按钮 start
和 end
的应用程序。开始按钮将启动一个线程,该线程运行录音功能。该函数是使用sounddevice
和soundfile
库编写的。录音可以持续任意时间,用户可以随时按ctrl+c
停止。
所以,现在我想为end
按钮实现一个函数,以停止通过按下start
按钮启动的线程,或者该函数可以向线程发送ctrl+c
信号。因此,当前录制将停止。我不确定如何实现这一目标。任何帮助表示赞赏。
由两个.py组成的代码如下:
audio_record.py
import os
import signal
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
import queue
from PyQt5 import QtCore, QtGui, QtWidgets
import soundfile as sf
import sounddevice as sd
import mythreading
class Ui_MainWindow(object):
def __init__(self):
self.threadpool = QThreadPool()
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(640, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(280, 190, 75, 23))
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.start_button_func)
self.pushButton_1 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_1.setGeometry(QtCore.QRect(380, 190, 75, 23))
self.pushButton_1.setObjectName("pushButton")
self.pushButton_1.clicked.connect(self.end_button_func)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 640, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "Start"))
self.pushButton_1.setText(_translate("MainWindow", "End"))
def record(self):
self.pid = os.getpid()
self.q = queue.Queue()
self.s = sd.InputStream(samplerate=48000, channels=2, callback=self.callback)
try:
# Make sure the file is open before recording begins
with sf.SoundFile('check.wav', mode='x', samplerate=48000, channels=2, subtype="PCM_16") as file:
with self.s:
# 1 second silence before the recording begins
time.sleep(1)
print('START')
print('#' * 80)
print('press Ctrl+C to stop the recording')
while True:
file.write(self.q.get())
except OSError:
print('The file to be recorded already exists.')
sys.exit(1)
def callback(self, indata, frames, time, status):
"""
This function is called for each audio block from the record function.
"""
if status:
print(status, file=sys.stderr)
self.q.put(indata.copy())
def start_button_func(self):
self.worker = mythreading.Worker(self.record)
self.threadpool.start(self.worker)
def end_button_func(self):
print('how to stop?')
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
mythreading.py如下:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
@pyqtSlot()
def run(self):
self.fn()
【问题讨论】:
【参考方案1】:你必须使用一个标志,在这种情况下threading.Event()
表示线程不应该再被执行。对于Ctrl + C
的情况,您必须使用QShortcut
import os
import queue
from PyQt5 import QtCore, QtGui, QtWidgets
import soundfile as sf
import sounddevice as sd
import mythreading
import threading
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(640, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(280, 190, 75, 23))
self.pushButton.setObjectName("pushButton")
self.pushButton_1 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_1.setGeometry(QtCore.QRect(380, 190, 75, 23))
self.pushButton_1.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 640, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "Start"))
self.pushButton_1.setText(_translate("MainWindow", "End"))
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.threadpool = QtCore.QThreadPool()
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
self.pushButton.clicked.connect(self.start_button_func)
self.pushButton_1.clicked.connect(self.end_button_func)
self.event_stop = threading.Event()
QtWidgets.QShortcut("Ctrl+C", self, activated=self.end_button_func)
def record(self):
self.pid = os.getpid()
self.q = queue.Queue()
self.s = sd.InputStream(samplerate=48000, channels=2, callback=self.callback)
try:
# Make sure the file is open before recording begins
with sf.SoundFile('check.wav', mode='x', samplerate=48000, channels=2, subtype="PCM_16") as file:
with self.s:
# 1 second silence before the recording begins
QtCore.QThread.sleep(1)
print('START')
print('#' * 80)
print('press Ctrl+C to stop the recording')
while not self.event_stop.is_set():
file.write(self.q.get())
print("STOP")
except OSError:
print('The file to be recorded already exists.')
sys.exit(1)
def callback(self, indata, frames, time, status):
if status:
print(status, file=sys.stderr)
self.q.put(indata.copy())
@QtCore.pyqtSlot()
def start_button_func(self):
print("start")
self.worker = mythreading.Worker(self.record)
self.threadpool.start(self.worker)
@QtCore.pyqtSlot()
def end_button_func(self):
print('how to stop?')
self.event_stop.set()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
【讨论】:
@eyllansec 我已经以另一种方式实现了。但你的想法也有效。谢谢。 @eyllansec 仅供参考,在您的实施中,如果我们要录制多个文件,则录制将不起作用。我已经相应地编辑了您的代码,以便它可以工作。 @M.Denis 在你的问题中你没有指出这个要求,所以我没有修改你在那部分的逻辑。如果你想实现它,逻辑是在你刻录新文件之前删除该文件(如果它存在),或者使用修改后的名称,如some_name(1).wav
@M.Denis 如果您要修改逻辑,请不要编辑任何答案,您的案例中的修改必须以非实质性方式进行,否则您会破坏我的答案。跨度>
以上是关于如何杀死 PyQt5 中的 QRunnable?的主要内容,如果未能解决你的问题,请参考以下文章
Qt Android,如何访问 QRunnable 中的外部对象?