python - 在 python 中使用上下文管理器超时进程
问题描述
我正在尝试编写一个上下文管理器来使进程超时,并且我有以下内容:
import threading
import _thread
import time
from contextlib import contextmanager
@contextmanager
def raise_timeout(timeout):
timer = threading.Timer(timeout, _thread.interrupt_main)
timer.start()
try:
yield
except:
print(f"timeout after {timeout} seconds")
raise
finally:
timer.cancel()
with raise_timeout(1):
time.sleep(5)
问题是当达到超时时,它仍然等待在time.sleep(5)
引发异常之前完成。当我运行脚本时,我等待了 5 秒钟,然后在终端中获得以下输出:
> timeout after 1 seconds Traceback (most recent call last): File
> "scratch1.py",
> line xx, in <module>
> time.sleep(5) KeyboardInterrupt
但是如果我如下所示直接运行计时器,我会在 1 秒超时后立即出现异常。
timer = threading.Timer(1, _thread.interrupt_main)
timer.start()
我只是想知道 with 语句中的进程如何阻止异常直到它完成?
解决方案
我认为您的Timer
解决方案无法正常运行,因为time.sleep
调用时不会被杀死_thread.interrupt_main()
。
_thread
文档对它的功能提出了建议:
线程与中断的交互很奇怪:
KeyboardInterrupt
异常将被任意线程接收。(当信号模块可用时,中断总是转到主线程。)
尽管Python 的错误跟踪器上的一个问题表明文档_thread.interrupt_main()
不精确。
应该注意的是,替换一个长时间运行的进程而不是time.sleep
您的上下文管理器可以按预期工作。
尝试:
with raise_timeout(1):
for _ in range(1_000_000_000):
pass
它会引发一个KeyboardInterrupt
如果可移植性不是问题,这里有一个解决方案signal
,它只在 Unix 上可用,因为它使用SIGALARM
.
import signal
import time
from contextlib import contextmanager
class TimeOutException(Exception):
pass
@contextmanager
def raise_timeout(timeout):
def _handler(signum, frame):
raise TimeOutException()
signal.signal(signal.SIGALRM, _handler)
signal.alarm(timeout)
try:
yield
except TimeOutException:
print(f"Timeout after {timeout} seconds")
raise
finally:
signal.alarm(0)
with raise_timeout(1):
time.sleep(2)
这将引发TimeOutException
推荐阅读
- animation - 如何判断glTF是否有动画数据
- angular - 如何组织组件之间的关系?
- c# - Rendered.enabled:NullReferenceException:对象引用未设置为对象的实例
- smartcontracts - 什么是智能合约创建 TX?
- wolfram-mathematica - 积分期间 0.5 和 1/2 之间的差异
- javascript - 将复选框/文本框值相乘的功能无法正常工作
- postgresql - 返回今天日期的行数,按小时分组,带 0 的小时数,无计数
- angular - 将表单组数据加载到锚标记中
- java - Java8:流合并两个列表
- excel - 基于多个过滤器创建工作表