【问题标题】:Processes started with Python subprocess module are not killed when app exits应用程序退出时不会终止使用 Python 子进程模块启动的进程
【发布时间】:2014-08-16 03:00:50
【问题描述】:

当我退出我的应用程序(下面的代码)时,我开始使用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_())  

【问题讨论】:

  • 你能不能把你的代码减少到最小的问题。这里的想法是让问题尽可能笼统,以便他们可以帮助尽可能多的人。
  • @dano 感谢您的建议。问题是我是 Python 的新手,在阅读了你提供的帖子后,我不知道如何在我的代码中使用。在我的代码中,主线程-> 线程-> 子进程。我被困在这里了。

标签: python subprocess


【解决方案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 来处理它,或者如果您通过 CLI 运行,它会执行 Ctrl+C 之类的操作。

编辑:

要处理关闭时遇到的异常,您必须更改代码处理从子进程的stdout 读取的数据的方式。当您在关闭时终止子进程时,它们会将None 发送到它们的stdout,并且您的线程会尝试处理该None,就好像它是实际数据一样。你只需要优雅地处理这种情况:

out = ret.stdout.read(1)
if not out:
    break
s += out 
print s
tryfind = getdata.findall(s)

【讨论】:

  • ,使用你的方式,可以杀死子进程。只是在杀死过程中出现了一些异常,没有得到标准输出,这里是修改的代码modified code
  • @soneedu 我编辑了我的答案,以展示如何避免在关机时看到的异常。
  • ,非常感谢你。我有 thougnt 检查出 False 或 True。但忘记休息。你是专业和耐心的。
【解决方案2】:

你有一个一般的设计问题:

  • 您可以在守护线程中启动子进程,而不必在程序结束时显式地停止它们
  • 但您希望进程停止

恕我直言,在您的情况下,最简单(也是最干净)的解决方案是明确要求您的线程停止并让它们终止启动的子进程:

  • 设置一个全局变量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
    

这应该足以正确杀死您的子进程。

【讨论】:

  • 我试试这个,但未能杀死子进程。谢谢你们一样
  • 这不起作用,因为 pinger 线程阻塞在 s+= ret.stdout.read(1),所以它永远不会循环查看 stopping 是否已设置。
  • @dano :由于管道的另一端有一个 ping,我希望下一个角色能带我去测试。
猜你喜欢
  • 1970-01-01
  • 2011-09-19
  • 1970-01-01
  • 2014-01-06
  • 1970-01-01
  • 2014-10-25
  • 1970-01-01
  • 1970-01-01
  • 2012-04-01
相关资源
最近更新 更多