signal.alarm无法实现通用timeout,因其仅主线程有效、不中断纯Python计算、与多线程/异步冲突;可靠方案是threading+queue(兼容所有同步代码)或asyncio.wait_for(要求awaitable)。

Python timeoutcontextmanager 的自实现

为什么不能直接用 signal.alarm 实现 timeoutcontextmanager

因为 signal.alarm 只在主线程生效,且会中断系统调用(比如 time.sleepsocket.recv),但对纯 Python 计算(如死循环、大列表推导)完全无效。更麻烦的是,它和多线程、异步代码根本冲突——一旦你在子线程里设 alarm,Python 会直接抛 ValueError: signal only works in main thread

所以自实现必须绕开信号机制,靠协程或线程协作式中断,或者依赖目标操作自身支持超时(比如 requests.get(timeout=...))。

用 threading.Thread + queue 实现最简可靠版

这是兼容性最强的方案:新开线程执行目标函数,主线程用 queue.Queue.get(timeout=...) 等结果。线程结束后自动释放,不用手动清理。

示例核心逻辑:

import threading
import queue

def timeout_contextmanager(seconds):
    def decorator(func):
        def wrapper(*args, **kwargs):
            q = queue.Queue()
            def _target():
                try:
                    result = func(*args, **kwargs)
                    q.put(('success', result))
                except Exception as e:
                    q.put(('error', e))
            t = threading.Thread(target=_target, daemon=True)
            t.start()
            try:
                status, value = q.get(timeout=seconds)
                if status == 'success':
                    return value
                raise value
            except queue.Empty:
                raise TimeoutError(f'Function {func.__name__} timed out after {seconds}s')
        return wrapper
    return decorator

asyncio 版本要注意 event loop 生命周期

如果你的函数本身是 async 的,用 asyncio.wait_for 最干净。但注意:它只能在已有 event loop 的上下文中运行,不能在已关闭或未启动的 loop 里调用。

常见错误现象:RuntimeError: no running event loopRuntimeWarning: coroutine ... was never awaited

别忽略 timeout 对异常传播的影响

超时不是“取消”,而是“放弃等待”。原函数如果在超时后仍继续执行并抛异常,这个异常不会被捕获,也不会传给上层——它就在那个孤立线程里静默消失(除非你手动 log)。

本文转载于:互联网 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。