Files
mai-bot/src/maisaka/input_reader.py
2026-03-23 17:55:39 +08:00

57 lines
1.9 KiB
Python

"""
MaiSaka - 异步输入读取器
将阻塞的标准输入读取放到后台线程中,供 asyncio 循环安全消费。
"""
import asyncio
import sys
import threading
from typing import Optional
class InputReader:
"""后台读取标准输入,并通过 asyncio.Queue 向主循环投递结果。"""
def __init__(self) -> None:
self._loop: Optional[asyncio.AbstractEventLoop] = None
self._queue: asyncio.Queue[Optional[str]] = asyncio.Queue()
self._thread: Optional[threading.Thread] = None
self._stop_event = threading.Event()
def start(self, loop: asyncio.AbstractEventLoop) -> None:
"""启动后台输入线程。重复调用时忽略。"""
if self._thread and self._thread.is_alive():
return
self._loop = loop
self._stop_event.clear()
self._thread = threading.Thread(target=self._read_loop, name="maisaka-input-reader", daemon=True)
self._thread.start()
def _read_loop(self) -> None:
"""在后台线程中阻塞读取 stdin。"""
while not self._stop_event.is_set():
line = sys.stdin.readline()
if self._loop is None:
return
if line == "":
self._loop.call_soon_threadsafe(self._queue.put_nowait, None)
return
self._loop.call_soon_threadsafe(self._queue.put_nowait, line.rstrip("\r\n"))
async def get_line(self, timeout: Optional[int] = None) -> Optional[str]:
"""异步获取一行输入;设置 timeout 时支持超时返回。"""
if timeout is None:
return await self._queue.get()
try:
return await asyncio.wait_for(self._queue.get(), timeout=timeout)
except asyncio.TimeoutError:
return None
def close(self) -> None:
"""请求后台线程停止。"""
self._stop_event.set()