Python 使用PyQt以图形方式显示尾部文件

Python 使用PyQt以图形方式显示尾部文件,python,multithreading,qt,pyqt,Python,Multithreading,Qt,Pyqt,我试图做一些听起来相当简单的事情,但我总是遇到各种各样的问题。 我正在尝试创建一个GUI,它可以使用PyQt同时跟踪多个文件。 我看到了这个关于如何用纯Python跟踪文件的答案 我曾尝试在QThread中使用此代码。 我在这里遇到的问题是,尾部过程永远不会自行停止;它需要被杀死。 当GUI关闭时,它应该被终止。 我在下面这个具体解决方案中遇到的其他问题是 QThread: Destroyed while thread is still running 及 及 我尝试过的其他实现都有尾部进程

我试图做一些听起来相当简单的事情,但我总是遇到各种各样的问题。 我正在尝试创建一个GUI,它可以使用PyQt同时跟踪多个文件。 我看到了这个关于如何用纯Python跟踪文件的答案

我曾尝试在QThread中使用此代码。 我在这里遇到的问题是,尾部过程永远不会自行停止;它需要被杀死。 当GUI关闭时,它应该被终止。 我在下面这个具体解决方案中遇到的其他问题是

QThread: Destroyed while thread is still running

我尝试过的其他实现都有尾部进程抱怨管道破裂,但一旦我执行stderr=pipe,它们就不再出现了。 我现在担心我可能会丢失错误,因为我从未从stderr读取数据(因为它会阻塞并且不应该有任何输出)

要获得错误,请尝试跟踪3个不同的文件。 我编写了另一个脚本,循环并写入这3个文件,睡眠时间为0.1秒。 我关闭GUI并一次又一次地启动它。有时我会出错,有时我不会

请告诉我我做错了什么

#!/usr/bin/env python

from PyQt4.QtCore import *
from PyQt4.QtGui import *

import os
from subprocess import Popen, PIPE

class Tailer(QThread):

    def __init__(self, fname, parent=None):
        super(Tailer, self).__init__(parent)
        self.fname = fname
        self.connect(self, SIGNAL('finished()'), self.cleanup)

    def cleanup(self):
        print 'CLEANING UP'
        self.p.kill()
        print 'killed'

    def run(self):
        command = ["tail", "-f", self.fname]
        print command
        self.p = Popen(command, stdout=PIPE, stderr=PIPE)
        while True:
            line = self.p.stdout.readline()
            self.emit(SIGNAL('newline'), line.rstrip())
            if not line:
                print 'BREAKING'
                break

    def foo(self):
        self.p.kill()

class TailWidget(QWidget):
    def __init__(self, fnames, parent=None):
        super(TailWidget, self).__init__(parent)
        layout = QGridLayout()
        self.threads = {}
        self.browsers = {}
        for i, fname in enumerate(fnames):
            if not os.path.exists(fname):
                print fname, "doesn't exist; creating"
                p = Popen(['touch', fname], stdout=PIPE, stderr=PIPE)
                out, err = p.communicate()
                ret = p.wait()
                assert ret == 0
            t = Tailer(fname, self)
            self.threads[fname] = t
            b = QTextBrowser()
            self.browsers[fname] = b
            layout.addWidget(QLabel('Tail on %s' % fname), 0, i)
            layout.addWidget(b, 1, i)
            self.connect(t, SIGNAL("newline"), b.append)
            t.start()
        self.setLayout(layout)

    def closeEvent(self, event):
        for fname, t in self.threads.items():
            t.foo()

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    tw = TailWidget(sys.argv[1:])
    tw.show()
    sys.exit(app.exec_())

问题是主线程没有在后台线程上等待

你告诉他们停在这里:

def closeEvent(self, event):
    for fname, t in self.threads.items():
        t.foo()
因此,这将杀死所有的子进程,这将使所有后台线程最终退出。但这不会让他们立即停止。直到下一次每个人都到达其
读线
,这种情况才会发生

终止子进程后,返回,让Qt立即关闭窗口并销毁小部件。任何试图向该小部件发送信号的后台线程都将失败


当线程0试图关闭时,假设线程1已经完成了<代码>读取行< /代码>,并处于其<>代码> RabaS/<代码>的中间。因此,线程0终止线程1的子进程,然后删除主小部件。线程1完成其
rstrip
并调用
emit
,现在它正在发送到一个已删除的小部件。

您推荐什么解决方案?一个快速而肮脏的解决方案是在
closeEvent
中阻止线程,直到线程消失,但这很难看(理论上可能会阻止UI线程足够长的时间,导致搁浅)。或者,使用
deleteLater
对删除进行排序。或者您可以吞下
关闭
并添加另一个信号(或者类似地让
关闭
发生,但不要直接进入
退出
,以免破坏对象)。或者间接地执行
换行
信号(例如,通过发送到另一个对象的信号,或
weakref
),这样即使在主线程停止运行后,它仍然是安全的。或者使用
moveToThread
而不是重写
QThread.run
。但是,再次查看代码时,它实际上可能更基本。您是否可以添加
\uu del\uuu
方法,将某些内容同时打印到
Tailer
TailWidget
,并在发生崩溃和未发生崩溃时显示序列?因为它可能只是因为删除了
Tailer
而崩溃,而不是
TailWidget
,在这种情况下,只要让
Tailer
保持更长的活动时间就可以解决问题。
#!/usr/bin/env python

from PyQt4.QtCore import *
from PyQt4.QtGui import *

import os
from subprocess import Popen, PIPE

class Tailer(QThread):

    def __init__(self, fname, parent=None):
        super(Tailer, self).__init__(parent)
        self.fname = fname
        self.connect(self, SIGNAL('finished()'), self.cleanup)

    def cleanup(self):
        print 'CLEANING UP'
        self.p.kill()
        print 'killed'

    def run(self):
        command = ["tail", "-f", self.fname]
        print command
        self.p = Popen(command, stdout=PIPE, stderr=PIPE)
        while True:
            line = self.p.stdout.readline()
            self.emit(SIGNAL('newline'), line.rstrip())
            if not line:
                print 'BREAKING'
                break

    def foo(self):
        self.p.kill()

class TailWidget(QWidget):
    def __init__(self, fnames, parent=None):
        super(TailWidget, self).__init__(parent)
        layout = QGridLayout()
        self.threads = {}
        self.browsers = {}
        for i, fname in enumerate(fnames):
            if not os.path.exists(fname):
                print fname, "doesn't exist; creating"
                p = Popen(['touch', fname], stdout=PIPE, stderr=PIPE)
                out, err = p.communicate()
                ret = p.wait()
                assert ret == 0
            t = Tailer(fname, self)
            self.threads[fname] = t
            b = QTextBrowser()
            self.browsers[fname] = b
            layout.addWidget(QLabel('Tail on %s' % fname), 0, i)
            layout.addWidget(b, 1, i)
            self.connect(t, SIGNAL("newline"), b.append)
            t.start()
        self.setLayout(layout)

    def closeEvent(self, event):
        for fname, t in self.threads.items():
            t.foo()

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    tw = TailWidget(sys.argv[1:])
    tw.show()
    sys.exit(app.exec_())
def closeEvent(self, event):
    for fname, t in self.threads.items():
        t.foo()