在qthread中停止长时间运行的进程

时间:2017-12-21 09:09:45

标签: python multithreading python-3.x pyqt qthread

我正在尝试使用emit模块从youtube下载视频。我创建了一个简单的GUI来做一些工作,我需要当用户点击开始按钮时,线程将被调用并下载开始并使用read方法发送数据,当此数据到达Main函数时{ {1}}类,线程必须在从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_()

先谢谢了,我希望我能很好地解释这个问题。

1 个答案:

答案 0 :(得分:2)

通过在工作人员的self.exec_()方法中调用run(),在下载完成后,在此线程上启动一个新的事件循环,然后此事件循环继续运行。你不需要在这里使用事件循环,如果你想使用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相同)告诉它停止。