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