求助:基于Streamlit实现终端式文本RPG交互页面异常排查
问题分析与解决方案
问题根源
- Streamlit基于会话驱动和页面重渲染机制,原代码中的
while循环会持续阻塞主线程,导致页面一直处于加载状态,无法响应用户交互。 st.text_input在页面每次重渲染时都会重置,且无法在循环中实时捕获输入并传递给子进程。- 子进程的
stdout.readline()是阻塞式调用,会进一步卡住页面流程。
修复方案
利用Streamlit的会话状态(st.session_state)保存子进程实例、输出历史,改用非阻塞方式读取子进程输出,通过按钮触发输入提交,避免阻塞主线程。
完整修复代码
import streamlit as st import subprocess import select st.title("Danse Macabre 文本RPG") # 初始化会话状态 if "process" not in st.session_state: # 启动子进程,设置非阻塞的stdout/stderr st.session_state.process = subprocess.Popen( ["python", "danse_macabre.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, bufsize=1 ) st.session_state.output_history = [] # 非阻塞读取子进程的输出和错误 def read_non_blocking(stream): data = "" while select.select([stream], [], [], 0.1)[0]: line = stream.readline() if not line: break data += line return data # 读取新输出并更新历史 stdout_output = read_non_blocking(st.session_state.process.stdout) stderr_output = read_non_blocking(st.session_state.process.stderr) if stdout_output: st.session_state.output_history.append(stdout_output.strip()) if stderr_output: st.session_state.output_history.append(f"[ERROR] {stderr_output.strip()}") # 显示历史输出 st.text_area("游戏输出", value="\n".join(st.session_state.output_history), height=400, disabled=True) # 用户输入区域 user_input = st.text_input("输入你的选择", key="user_input") if st.button("提交") and user_input and st.session_state.process.poll() is None: # 传递输入给子进程 st.session_state.process.stdin.write(user_input + "\n") st.session_state.process.stdin.flush() # 清空输入框(通过更新会话状态触发重渲染) st.session_state.user_input = "" # 检查子进程是否结束 if st.session_state.process.poll() is not None: st.info("游戏已结束")
关键修复点说明
- 会话状态存储:用
st.session_state保存子进程实例和输出历史,确保页面重渲染时不会重复启动进程或丢失输出。 - 非阻塞读取:通过
select模块实现非阻塞的输出读取,避免阻塞Streamlit的主线程。 - 输入提交机制:用按钮触发输入提交,防止输入框每次重渲染重置,同时避免重复传递输入给子进程。
- 统一输出区域:用
st.text_area展示所有历史输出,避免创建大量独立的st.text组件,提升页面性能。
内容的提问来源于stack exchange,提问作者Taiko
相关产品推荐
相关产品推荐

