使用Python子进程模块启动的进程在应用退出时未被终止
当我退出我的应用程序(下面的代码),我用 subprocess.Popen
启动的两个 ping 进程并不会自动结束,仍然会在 Windows 7 的任务列表中显示。
在应用程序运行时,这两个 ping 进程会作为两个线程显示在 Python.exe 下面。当应用程序退出时,这两个进程会转到系统进程标签下,并继续运行。
我该如何解决这个问题?我希望在我的应用程序关闭时,这两个 ping 进程能够被结束。
# -*- coding: utf-8 -*-
import sys
import time
import subprocess
from threading import Thread
import re
from PyQt4.QtGui import QMainWindow, QApplication, QStandardItemModel, QStandardItem, QWidget, QVBoxLayout, QTableView
from PyQt4.QtCore import pyqtSignature, Qt, QTimer, SIGNAL, QString, QMetaObject
from Queue import Queue
try:
_fromUtf8 = QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(500, 435)
self.centralWidget = QWidget(MainWindow)
self.centralWidget.setObjectName(_fromUtf8("centralWidget"))
self.verticalLayout = QVBoxLayout(self.centralWidget)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.tableView = QTableView(self.centralWidget)
self.tableView.setObjectName(_fromUtf8("tableView"))
self.verticalLayout.addWidget(self.tableView)
MainWindow.setCentralWidget(self.centralWidget)
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QApplication.translate("MainWindow", "Ping Tester", None, QApplication.UnicodeUTF8))
if sys.platform.startswith('linux'):
getdata = re.compile(r"icmp_req=(\d+) ttl=(\d+) time=([\d\.]+)\sms")
pingstr = ["ping", "-n", "-i 0.2"]
filtered = "Packet filtered"
delaytime = 200
else:
getdata = re.compile(r"=([\d\.]+)ms TTL=(\d+)")
pingstr = ["ping.exe", "-t"]
timeout = "Request timed out."
delaytime = 500
try:
with open("ips.conf", "r") as f:
t_node = f.read().decode('utf-8')
if not t_node:
raise IOError
except IOError:
with open("ips.conf", "w") as f:
t_node = u"""
8.8.8.8-Google
184.22.112.34-USAHE
"""
f.write(t_node.encode('utf-8'))
node = []
for line in t_node.split('\n'):
try:
ip, desc = line.strip().split("-")
node.append((ip, desc))
except ValueError:
pass
nodecount = len(node)
class MainWindow(QMainWindow, Ui_MainWindow):
"""
Class documentation goes here.
"""
def __init__(self, parent = None):
"""
Constructor
"""
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.model = QStandardItemModel()
self.model.setColumnCount(6)
self.model.setRowCount(nodecount)
self.model.setHorizontalHeaderLabels(["IP", "Description", "Loss%", "CurPing", "AvgPing", "TTL"])
for i, (ip, desc) in enumerate(node):
self.setitem(i, 0, ip)
self.setitem(i, 1, desc)
self.setitem(i, 2, "")
self.setitem(i, 3, "")
self.setitem(i, 4, "")
self.setitem(i, 5, "")
self.tableView.setModel(self.model)
for i in range(len(node)):
self.tableView.setRowHeight(i, 18)
self.resizetable()
self.timer = QTimer(self)
self.connect(self.timer,
SIGNAL("timeout()"),
self.checkitems)
self.timer.start(delaytime)
def checkitems(self):
while not q.empty():
item = q.get()
self.chgtxt(*item)
q.task_done()
self.resizetable()
def resizetable(self):
self.tableView.resizeColumnsToContents()
def chgtxt(self, x, y, value):
self.model.item(x, y).setText(value)
def setitem(self, x, y, value):
self.model.setItem(x, y, QStandardItem(value))
app = QApplication(sys.argv)
ui = MainWindow()
ui.show()
q = Queue()
def pinger(i, ip, desc):
s = ""
avgping = 0
count = 0
timeoutcount = 0
ret = subprocess.Popen(pingstr + [ip],
stdout=subprocess.PIPE)
while True:
try:
s += ret.stdout.read(1)
tryfind = getdata.findall(s)
if sys.platform.startswith('linux'):
if len(tryfind) > 0:
req, ttl, crtping = tryfind[-1]
avgping += float(crtping)
count += 1
q.put((i, 3, crtping + "ms"))
q.put((i, 4, "%.2f" % (avgping * 1.0 / count) + "ms"))
q.put((i, 5, ttl))
q.put((i, 2, "%.2f" % ((int(req) - count) * 100.0 / int(req))))
s = ""
elif filtered in s:
q.put((i, 2, "Failed"))
q.put((i, 3, "Failed"))
q.put((i, 4, "Failed"))
q.put((i, 5, "Failed"))
ret.kill()
s = ""
else:
if len(tryfind) > 0:
crtping, ttl = tryfind[-1]
avgping += float(crtping)
count += 1
q.put((i, 3, crtping + "ms"))
q.put((i, 4, "%.2f" % (avgping * 1.0 / count) + "ms"))
q.put((i, 5, ttl))
q.put((i, 2, "%.2f" % (timeoutcount * 100.0 / (count + timeoutcount))))
elif timeout in s:
timeoutcount += 1
q.put((i, 2, "-"))
q.put((i, 3, "-"))
if count:
q.put((i, 5, "%.2f" % (timeoutcount * 100.0 / (count + timeoutcount))))
else:
q.put((i, 5, "-"))
s = ""
except IOError:
print s
break
def startworkers():
for i, (ip, desc) in enumerate(node):
worker = Thread(target=pinger, args=(i, ip, desc))
worker.setDaemon(True)
worker.start()
time.sleep(delaytime / 10000.0)
startthread = Thread(target=startworkers)
startthread.setDaemon(True)
startthread.start()
sys.exit(app.exec_())
2 个回答
0
你遇到了一个设计上的问题:
- 你在后台线程中启动了子进程,这样在程序结束时就不需要手动停止它们
- 但你希望这些进程能够停止
在我看来,最简单(也是最干净)的解决办法就是明确要求你的线程停止,并让它们终止自己启动的子进程:
- 设置一个全局变量
global stopping = False
- 使用非后台线程
在最后,把你的
sys.exit(app.exec_())
替换为cr = app_exec_() stopping = true sys.exit(cr)
在 pinger 中,把
while True:
替换为global stopping while True: if stopping: ret.kill() break
这样就足够正确地终止你的子进程了。
1
这里有一种方法可以实现这个功能,使用 atexit
:
import subprocess
from threading import Thread
import sys
import atexit
from PyQt4.QtGui import QMainWindow, QApplication
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(("MainWindow"))
MainWindow.resize(500, 435)
def runproc():
p = subprocess.Popen(["sleep", "500"])
atexit.register(kill_proc, p)
p.communicate()
def kill_proc(proc):
try:
proc.terminate()
except Exception:
pass
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.resize(300, 300)
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = MainWindow()
ui.show()
for i in range(0, 3):
t = Thread(target=runproc)
t.start()
sys.exit(app.exec_())
每个线程都会注册一个 atexit
回调,这个回调会传入它创建的 Popen
对象。当进程正常退出时,atexit
的处理函数会被调用,在每个处理函数中,我们会对 Popen
对象调用 terminate
,这样就可以结束这个进程。需要注意的是,这种方法并不能处理像 SIGKILL 这样的信号被发送到你的进程;它只处理通过关闭 QMainWindow
或者在命令行中按 Ctrl+C 这种方式退出的情况。
编辑:
为了处理你在关闭时遇到的异常,你需要改变代码处理从子进程的 stdout
读取数据的方式。当你在关闭时杀掉子进程时,它们会向 stdout
发送 None
,而你的线程会试图将这个 None
当作实际数据来处理。你只需要优雅地处理这种情况:
out = ret.stdout.read(1)
if not out:
break
s += out
print s
tryfind = getdata.findall(s)