Skip to content

Commit dbe9675

Browse files
committed
fix: 修复ctrl+c无法关闭程序的问题
1 parent 4aaca5b commit dbe9675

5 files changed

Lines changed: 150 additions & 48 deletions

File tree

main.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from src.sensor.danmaku_mock_sensor import danmaku_mock_sensor
33
from src.utils.logger import get_logger
44
import sys
5+
import signal
6+
import os
57
from src.neuro.core import core
68

79
logger = get_logger("main")
@@ -17,29 +19,66 @@ async def boot():
1719

1820
async def halt():
1921
try:
20-
logger.info("正在关闭Adapter...")
22+
logger.info("正在关闭系统...")
23+
24+
# 先关闭传感器
25+
await danmaku_mock_sensor.disconnect()
26+
logger.info("弹幕传感器已关闭")
27+
28+
# 关闭核心
2129
await core.disconnect()
22-
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
23-
for task in tasks:
24-
if not task.done():
25-
task.cancel()
26-
await asyncio.gather(*tasks, return_exceptions=True)
30+
logger.info("核心已关闭")
31+
32+
# 取消所有剩余任务
33+
pending_tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]
34+
35+
logger.info(f"正在取消 {len(pending_tasks)} 个剩余任务")
36+
37+
# 打印所有未完成任务的名称,帮助调试
38+
for i, task in enumerate(pending_tasks):
39+
logger.info(f"待取消任务 {i + 1}: {task.get_name()} - {task}")
40+
41+
# 给每个任务发送取消信号
42+
for task in pending_tasks:
43+
task.cancel()
44+
45+
# 设置超时等待所有任务完成
46+
try:
47+
await asyncio.wait_for(asyncio.gather(*pending_tasks, return_exceptions=True), timeout=2.0)
48+
except asyncio.TimeoutError:
49+
logger.warning("部分任务取消超时,强制退出")
50+
# 打印超时后仍未完成的任务
51+
still_pending = [t for t in pending_tasks if not t.done()]
52+
for i, task in enumerate(still_pending):
53+
logger.warning(f"超时未取消任务 {i + 1}: {task.get_name()} - {task}")
54+
55+
logger.info("所有任务已关闭")
2756

2857
except Exception as e:
29-
logger.error(f"Adapter关闭失败: {e}")
58+
logger.error(f"关闭系统失败: {e}")
59+
import traceback
60+
61+
logger.error(traceback.format_exc())
3062

3163

3264
if __name__ == "__main__":
65+
# 设置信号处理
3366
loop = asyncio.new_event_loop()
3467
asyncio.set_event_loop(loop)
68+
3569
try:
3670
loop.run_until_complete(boot())
71+
loop.run_forever()
3772
except KeyboardInterrupt:
3873
logger.warning("收到中断信号,正在关闭...")
3974
loop.run_until_complete(halt())
75+
loop.close()
76+
logger.info("程序已完全退出,强制结束所有进程")
77+
# 强制结束程序
78+
os._exit(0) # 使用os._exit强制退出,不会等待其他线程
4079
except Exception as e:
4180
logger.error(f"主程序异常: {str(e)}")
4281
if loop and not loop.is_closed():
4382
loop.run_until_complete(halt())
4483
loop.close()
45-
sys.exit(1)
84+
os._exit(1)

src/actuator/subtitle_actuator.py

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,30 @@ async def disconnect(self):
5252
断开连接
5353
"""
5454
logger.info("字幕执行器正在断开连接...")
55-
self.running = False
56-
57-
# 取消订阅
55+
# 先取消订阅,这样不会再有新消息
5856
await self.synapse.unsubscribe_output(self._handle_output)
57+
logger.info("字幕执行器已取消订阅输出")
5958

60-
# 关闭字幕
61-
if self.subtitle:
62-
self.subtitle.close()
63-
self.subtitle = None
59+
# 停止处理消息的循环
60+
self.running = False
6461

65-
logger.info("字幕执行器已断开连接")
62+
# 关闭字幕
63+
try:
64+
if self.subtitle and hasattr(self.subtitle, "close"):
65+
self.subtitle.close()
66+
self.subtitle = None
67+
logger.info("字幕窗口已关闭")
68+
except Exception as e:
69+
logger.error(f"关闭字幕窗口时出错: {e}")
70+
71+
# 最多等待1秒让线程自行结束
72+
if self.subtitle_thread and self.subtitle_thread.is_alive():
73+
logger.info("等待字幕线程结束...")
74+
self.subtitle_thread.join(timeout=1.0)
75+
if self.subtitle_thread.is_alive():
76+
logger.warning("字幕线程未能正常结束,但将继续关闭流程")
77+
78+
logger.info("字幕执行器已完全断开连接")
6679

6780
async def _handle_output(self, neurotransmitter: Neurotransmitter):
6881
"""
@@ -84,32 +97,52 @@ def _run_subtitle(self):
8497
"""
8598
运行字幕窗口
8699
"""
87-
# 创建字幕
88-
self.subtitle = MultiMessageSubtitle(
89-
text="等待消息...",
90-
theme="dark",
91-
font_family="Microsoft YaHei",
92-
font_size=24,
93-
text_color="#FFFFFF",
94-
bg_color="#333333",
95-
opacity=0.7,
96-
animation_speed=10,
97-
border_radius=10,
98-
padding=15,
99-
show_history=self.show_history,
100-
)
101-
102-
# 设置初始位置(屏幕底部)
103-
screen_width = self.subtitle.root.winfo_screenwidth()
104-
screen_height = self.subtitle.root.winfo_screenheight()
105-
subtitle_width = 600
106-
subtitle_height = 200
107-
x_pos = (screen_width - subtitle_width) // 2
108-
y_pos = screen_height - subtitle_height - 50
109-
self.subtitle.root.geometry(f"{subtitle_width}x{subtitle_height}+{x_pos}+{y_pos}")
110-
111-
# 运行字幕
112-
self.subtitle.run()
100+
try:
101+
# 创建字幕
102+
self.subtitle = MultiMessageSubtitle(
103+
text="等待消息...",
104+
theme="dark",
105+
font_family="Microsoft YaHei",
106+
font_size=24,
107+
text_color="#FFFFFF",
108+
bg_color="#333333",
109+
opacity=0.7,
110+
animation_speed=10,
111+
border_radius=10,
112+
padding=15,
113+
show_history=self.show_history,
114+
)
115+
116+
# 设置初始位置(屏幕底部)
117+
screen_width = self.subtitle.root.winfo_screenwidth()
118+
screen_height = self.subtitle.root.winfo_screenheight()
119+
subtitle_width = 600
120+
subtitle_height = 200
121+
x_pos = (screen_width - subtitle_width) // 2
122+
y_pos = screen_height - subtitle_height - 50
123+
self.subtitle.root.geometry(f"{subtitle_width}x{subtitle_height}+{x_pos}+{y_pos}")
124+
125+
# 添加周期性检查,当running变为False时退出循环
126+
def check_running():
127+
if not self.running and self.subtitle and self.subtitle.root:
128+
try:
129+
self.subtitle.root.quit()
130+
return # 不再继续调度
131+
except Exception as e:
132+
logger.error(f"退出字幕循环时出错: {e}")
133+
134+
# 如果仍在运行,继续检查
135+
if self.subtitle and self.subtitle.root:
136+
self.subtitle.root.after(100, check_running)
137+
138+
# 开始检查循环
139+
self.subtitle.root.after(100, check_running)
140+
141+
# 运行字幕
142+
self.subtitle.run()
143+
144+
except Exception as e:
145+
logger.error(f"字幕线程出错: {e}", exc_info=True)
113146

114147
async def _process_messages(self):
115148
"""

src/neuro/synapse.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,17 @@ async def unsubscribe_output(self, handler: Callable):
6666
"""
6767
取消订阅输出神经递质
6868
"""
69-
if handler in self.output_handlers:
70-
self.output_handlers.remove(handler)
71-
logger.debug(f"已移除输出处理器: {handler.__name__}")
69+
try:
70+
if handler in self.output_handlers:
71+
self.output_handlers.remove(handler)
72+
logger.debug(f"已移除输出处理器: {handler.__name__}")
73+
else:
74+
logger.debug(
75+
f"尝试移除不存在的输出处理器: {handler.__name__ if hasattr(handler, '__name__') else handler}"
76+
)
77+
except Exception as e:
78+
logger.error(f"移除输出处理器时出错: {e}")
79+
# 即使出错也继续执行,不阻塞关闭流程
7280

7381

7482
# 创建全局突触实例

src/sensor/danmaku_mock_sensor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@ async def connect(self):
4848
)
4949
await asyncio.sleep(10)
5050

51+
async def disconnect(self):
52+
"""停止模拟消息生成"""
53+
logger.info("正在关闭弹幕模拟传感器...")
54+
self.running = False
55+
5156

5257
danmaku_mock_sensor = DanmakuMockSensor(synapse)

src/utils/subtitle.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,27 @@ def run(self):
239239

240240
def close(self):
241241
"""关闭窗口"""
242+
# 停止动画线程
242243
self.animation_running = False
243-
if self.animation_thread:
244-
self.animation_thread.join()
245-
self.root.destroy()
244+
if self.animation_thread and self.animation_thread.is_alive():
245+
try:
246+
self.animation_thread.join(timeout=0.5)
247+
except Exception as e:
248+
print(f"关闭动画线程时出错: {e}")
249+
250+
# 安全地销毁窗口
251+
try:
252+
# 使用after方法在主线程中执行destroy
253+
if self.root and self.root.winfo_exists():
254+
self.root.after(0, self.root.quit)
255+
self.root.after(100, self.root.destroy)
256+
except Exception as e:
257+
print(f"关闭窗口时出错: {e}")
258+
# 即使出错也要尝试强制销毁
259+
try:
260+
self.root.destroy()
261+
except:
262+
pass
246263

247264

248265
class MultiMessageSubtitle(Subtitle):

0 commit comments

Comments
 (0)