在 qthread 中停止 long-运行 进程
Stop long-running process in qthread
我正在尝试使用 youtube-dl
模块从 youtube 下载视频。我创建了一个简单的 GUI 来做一些小工作,我需要当用户单击开始按钮时调用线程并开始下载并使用 emit
方法发送数据,当此数据到达 read
函数时 Main
class,从 GUI 调用 stop
函数后线程必须停止,我尝试使用 exec_()
在 qthread 中创建事件循环并使用 exit
停止线程,并且我也尝试使用 terminate
但 GUI 冻结了。
我使用的代码是:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from youtube_dl import *
class Worker(QThread):
data = pyqtSignal(object)
def __init__(self):
super(Worker, self).__init__()
self.flag = True
def sendHook(self, data, status = {'status':'downloading'}):
self.data.emit(data)
def stop(self):
self.quit()
self.exit()
def run(self):
self.y = YoutubeDL({'progress_hooks':[self.sendHook]})
self.y.download(['https://www.youtube.com/watch?v=LKIXbNW-B5g'])
self.exec_()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
self.l = QLabel("Hello")
b = QPushButton("Start!")
b.pressed.connect(self.connectthread)
layout.addWidget(self.l)
layout.addWidget(b)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.show()
def read(self, data):
self.thread.stop()
def connectthread(self):
self.thread = Worker()
self.thread.data.connect(self.read)
self.thread.start()
app = QApplication([])
window = MainWindow()
app.exec_()
通过在你的 worker 的 run()
方法中调用 self.exec_()
你可以在这个线程上启动一个新的事件循环 在 下载完成之后,这个事件循环然后只保留 运行。你在这里不需要事件循环,如果你想使用他们的 moveToThread()
方法将 QObjects 移动到它以将它们与主事件循环分离,你只需要一个单独的事件循环,但这里不需要,你没有做任何需要 Qt 事件循环的事情。这也是为什么调用 stop()
或 exit()
不会做任何事情,它只会影响事件循环。停止这个线程的唯一方法是它的 terminate()
方法,这也有点工作:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from youtube_dl import *
class Worker(QThread):
data = pyqtSignal(object)
def __init__(self):
super(Worker, self).__init__()
self.flag = True
def sendHook(self, data, status = {'status':'downloading'}):
self.data.emit(data)
def stop(self):
self.terminate()
print("QThread terminated")
def run(self):
self.y = YoutubeDL({'progress_hooks':[self.sendHook]})
self.y.download(['https://www.youtube.com/watch?v=LKIXbNW-B5g'])
print("finished")
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
self.l = QLabel("Hello")
b = QPushButton("Start!")
b.pressed.connect(self.connectthread)
layout.addWidget(self.l)
layout.addWidget(b)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.thread = None
self.show()
def read(self, data):
print("read:", data)
def connectthread(self):
if self.thread is not None:
# already running
self.thread.stop()
self.thread = None
return
self.thread = Worker()
self.thread.data.connect(self.read)
self.thread.start()
app = QApplication([])
window = MainWindow()
app.exec_()
这里我修改了你的程序,所以第一次点击按钮时工作器启动,第二次线程终止等等。
然而,以这种方式终止线程是危险的,也是不鼓励的。 Python 线程通常需要协作才能停止,因为根据设计,它们无法被中断。在这种情况下,它之所以有效,是因为 PyQt 控制着线程。
遗憾的是,无法正常停止 youtube-dl 下载,请参阅 this related issue 了解更多信息。一般来说,不能保证杀死调用 download()
的线程会真正停止下载。 YoutubeDL 支持具有不同下载器的插件系统。例如,要下载 hls 流,将启动外部 ffmpeg
(或 avconv
)进程,不会通过终止工作线程来停止该进程。对于在内部使用其他线程或进程的下载器,或者也使用 ffmpeg
.
执行的 post 处理步骤,情况也是如此。
如果您希望能够安全地停止下载,您必须使用一个单独的进程,以便您可以使用 SIGINT
信号(与按 Ctrl-C 相同)来告诉它停止。
我正在尝试使用 youtube-dl
模块从 youtube 下载视频。我创建了一个简单的 GUI 来做一些小工作,我需要当用户单击开始按钮时调用线程并开始下载并使用 emit
方法发送数据,当此数据到达 read
函数时 Main
class,从 GUI 调用 stop
函数后线程必须停止,我尝试使用 exec_()
在 qthread 中创建事件循环并使用 exit
停止线程,并且我也尝试使用 terminate
但 GUI 冻结了。
我使用的代码是:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from youtube_dl import *
class Worker(QThread):
data = pyqtSignal(object)
def __init__(self):
super(Worker, self).__init__()
self.flag = True
def sendHook(self, data, status = {'status':'downloading'}):
self.data.emit(data)
def stop(self):
self.quit()
self.exit()
def run(self):
self.y = YoutubeDL({'progress_hooks':[self.sendHook]})
self.y.download(['https://www.youtube.com/watch?v=LKIXbNW-B5g'])
self.exec_()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
self.l = QLabel("Hello")
b = QPushButton("Start!")
b.pressed.connect(self.connectthread)
layout.addWidget(self.l)
layout.addWidget(b)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.show()
def read(self, data):
self.thread.stop()
def connectthread(self):
self.thread = Worker()
self.thread.data.connect(self.read)
self.thread.start()
app = QApplication([])
window = MainWindow()
app.exec_()
通过在你的 worker 的 run()
方法中调用 self.exec_()
你可以在这个线程上启动一个新的事件循环 在 下载完成之后,这个事件循环然后只保留 运行。你在这里不需要事件循环,如果你想使用他们的 moveToThread()
方法将 QObjects 移动到它以将它们与主事件循环分离,你只需要一个单独的事件循环,但这里不需要,你没有做任何需要 Qt 事件循环的事情。这也是为什么调用 stop()
或 exit()
不会做任何事情,它只会影响事件循环。停止这个线程的唯一方法是它的 terminate()
方法,这也有点工作:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from youtube_dl import *
class Worker(QThread):
data = pyqtSignal(object)
def __init__(self):
super(Worker, self).__init__()
self.flag = True
def sendHook(self, data, status = {'status':'downloading'}):
self.data.emit(data)
def stop(self):
self.terminate()
print("QThread terminated")
def run(self):
self.y = YoutubeDL({'progress_hooks':[self.sendHook]})
self.y.download(['https://www.youtube.com/watch?v=LKIXbNW-B5g'])
print("finished")
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
self.l = QLabel("Hello")
b = QPushButton("Start!")
b.pressed.connect(self.connectthread)
layout.addWidget(self.l)
layout.addWidget(b)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.thread = None
self.show()
def read(self, data):
print("read:", data)
def connectthread(self):
if self.thread is not None:
# already running
self.thread.stop()
self.thread = None
return
self.thread = Worker()
self.thread.data.connect(self.read)
self.thread.start()
app = QApplication([])
window = MainWindow()
app.exec_()
这里我修改了你的程序,所以第一次点击按钮时工作器启动,第二次线程终止等等。
然而,以这种方式终止线程是危险的,也是不鼓励的。 Python 线程通常需要协作才能停止,因为根据设计,它们无法被中断。在这种情况下,它之所以有效,是因为 PyQt 控制着线程。
遗憾的是,无法正常停止 youtube-dl 下载,请参阅 this related issue 了解更多信息。一般来说,不能保证杀死调用 download()
的线程会真正停止下载。 YoutubeDL 支持具有不同下载器的插件系统。例如,要下载 hls 流,将启动外部 ffmpeg
(或 avconv
)进程,不会通过终止工作线程来停止该进程。对于在内部使用其他线程或进程的下载器,或者也使用 ffmpeg
.
如果您希望能够安全地停止下载,您必须使用一个单独的进程,以便您可以使用 SIGINT
信号(与按 Ctrl-C 相同)来告诉它停止。