Python2.7中Tkinter读取subprocess标准输出时界面冻结如何解决
问题根源
有两个独立原因导致界面卡死、输出批量返回:
- Python子进程默认启用块缓冲:当输出目标不是终端时,
print内容会暂存到缓冲区(通常4KB)满后才会写入管道,因此子进程10秒运行结束后才会一次性输出所有内容 - 主程序读逻辑阻塞:
for line in iter(self.myprocess.stdout.readline, '')会持续阻塞等待子进程输出直到管道关闭(子进程退出),Tkinter主线程卡在该循环中无法响应界面交互
解决方案
1. 禁用子进程输出缓冲
二选一即可:
- 方案A:启动子进程时添加
-u参数,强制Python关闭标准输出缓冲
修改startProcess方法:def startProcess(self): # 用列表形式传参数,避免shell=True的安全风险 command = ["python", "-u", "spammer.py"] self.myprocess = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=1) - 方案B:修改
spammer.py,每次打印后手动刷新缓冲# spammer.py import time import sys counter = 0 while counter < 40: print("counter = %d" % counter) sys.stdout.flush() # 新增行,手动刷缓冲 counter += 1 time.sleep(0.25)
2. 修正主程序读逻辑,避免阻塞
Linux/Unix平台方案
通过fcntl将管道设置为非阻塞模式,每次定时调用时仅读取当前已有的内容,无新内容直接返回:
def getLogText(self): if self.myprocess is None: return # 子进程已退出时读完剩余内容 if self.myprocess.poll() is not None: for line in self.myprocess.stdout.readlines(): self.logmessages.append(line.strip()) self.myprocess = None return # 设置非阻塞读 import fcntl, os fd = self.myprocess.stdout.fileno() fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) try: line = self.myprocess.stdout.readline() if line: self.logmessages.append(line.strip()) except: # 无新内容直接返回 return
跨平台兼容方案(支持Windows)
Windows系统无fcntl模块,可用独立线程读取子进程输出,内容存入线程安全队列,主线程定时从队列取数:
- 新增依赖导入
import threading import Queue - 初始化队列,在
__init__方法中新增self.queue = Queue.Queue() - 新增子进程读逻辑
def read_process_output(self, proc, queue): for line in iter(proc.stdout.readline, ''): queue.put(line.strip()) proc.wait() - 修改
startProcess启动读线程def startProcess(self): command = ["python", "-u", "spammer.py"] self.myprocess = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=1) # 启动后台读线程 self.read_thread = threading.Thread(target=self.read_process_output, args=(self.myprocess, self.queue)) self.read_thread.daemon = True self.read_thread.start() - 改写
getLogText从队列取数def getLogText(self): while not self.queue.empty(): line = self.queue.get() self.logmessages.append(line)
3. 修复已知笔误
原代码treatOutput方法中存在属性名拼写错误,修正后:
def treatOutput(self): while len(self.logmessages): # 原代码为self.logmessage,缺末尾的s,修正 line = self.logmessages.pop(0) line = line.replace("counter = ", "") mynumber = int(line) if mynumber % 3: print(mynumber)
修改完成后即可实现实时读取子进程输出,界面全程无阻塞,Stop按钮可正常响应。
内容的提问来源于stack exchange,提问作者Carlos Adir
相关产品推荐
相关产品推荐

