使用Trio+Rich TUI时按strg-C后控制台光标消失的原因咨询
问题背景
我开发了一款硬件状态监控工具,结合异步代码与Rich TUI实现实时彩色控制台输出。工具通过Trio调度子进程、解析输出,Rich负责TUI渲染;为了让TUI实时更新逻辑与其他异步代码共存,我将TUI刷新逻辑封装在线程中,通过Trio nursery启动并设置abandon_on_cancel=True以支持Ctrl+C中断。
核心代码如下:
async with trio.open_nursery() as nursery: nursery.start_soon( lambda: trio.to_thread.run_sync(screen_layout_refresher, abandon_on_cancel=True)) nursery.start_soon(async_func1, params...) nursery.start_soon(async_func2, params...) nursery.start_soon(async_func3, params...)
def screen_layout_refresher(): with Live(the_tui_layout, refresh_per_second=1, screen=True): # the_tui_layout 是 rich.layout.Layout 实例 while True: # 这里更新TUI数据
功能运行正常,但按下Ctrl+C后控制台光标消失,甚至出现黑底黑光标(未主动设置黑色输出),影响后续控制台操作。
原因分析
光标消失的核心原因:
Rich的Live上下文管理器在进入TUI模式时会自动隐藏光标,退出with块时则会执行终端状态重置(包括恢复光标显示)。但abandon_on_cancel=True会在Ctrl+C触发nursery取消时强制终止线程,导致Live的__exit__清理逻辑完全没机会执行,终端就停留在光标隐藏的状态。黑底黑光标问题:
设置screen=True会让Rich切换到全屏终端模式,该模式会修改终端的全局颜色属性(比如背景色、光标颜色)。线程被强制终止后,这些颜色设置未被重置——如果光标颜色被改成与背景色一致(比如全屏模式默认背景为黑色,光标颜色同步被设为黑色),就会出现“黑底黑光标”的视觉效果:本质是光标存在但与背景色重合,再加上之前的隐藏状态,就彻底无法被识别。
修复方案
核心思路是让TUI刷新线程优雅退出,确保Rich能完整执行终端状态重置逻辑,避免使用abandon_on_cancel=True强制终止线程。
方案1:用终止事件控制循环退出
给screen_layout_refresher传入线程安全的终止事件,当捕获到KeyboardInterrupt时触发事件,让循环正常退出,从而让Live上下文管理器完成清理:
from threading import Event import time async def main(): stop_event = Event() async with trio.open_nursery() as nursery: # 启动TUI刷新线程,传递终止事件 nursery.start_soon( lambda: trio.to_thread.run_sync(screen_layout_refresher, stop_event) ) nursery.start_soon(async_func1, params...) nursery.start_soon(async_func2, params...) nursery.start_soon(async_func3, params...) try: await nursery.wait() except KeyboardInterrupt: stop_event.set() nursery.cancel_scope.cancel() def screen_layout_refresher(stop_event): with Live(the_tui_layout, refresh_per_second=1, screen=True): while not stop_event.is_set(): # 更新TUI数据 time.sleep(0.1) # 配合刷新频率,避免空转浪费资源
方案2:手动捕获信号并重置终端
如果必须保留abandon_on_cancel=True,可以手动捕获KeyboardInterrupt,调用Rich的终端重置方法恢复状态:
from rich.console import Console console = Console() async def main(): try: async with trio.open_nursery() as nursery: nursery.start_soon( lambda: trio.to_thread.run_sync(screen_layout_refresher, abandon_on_cancel=True) ) # ...其他异步任务 await nursery.wait() except KeyboardInterrupt: # 手动恢复光标显示、重置终端样式 console.show_cursor(True) console.reset_style()
注意事项
- 涉及终端状态修改的场景,尽量避免强制终止线程,优雅退出是最可靠的方式。
- Rich的
screen=True会修改终端全局状态,必须确保上下文管理器的退出逻辑能执行,否则容易留下终端异常状态。
内容的提问来源于stack exchange,提问作者Wör Du Schnaffzig

