# timeout_test1.py
from tqdm import trange
import sys
import time
import timeout_decorator
@timeout_decorator.timeout(int(sys.argv[2]))
def test():
if sys.argv[1] == '--timeout':
for i in trange(3):
time.sleep(1)
print ('>>> {} seconds passed.'.format(i+1))
return 0
if __name__ == '__main__':
try:
test()
except Exception as e:
print ('Timeout Error Catched!')
print (e)
print ("Timeout Task Ended!")
timeout-decorator装饰器的使用
该超时模块采用装饰器的形式来进行调用,使用时先import该模块,然后在需要设置定时任务的函数前添加@timeout_decorator.timeout(3)即可,这里括号中的3表示超时时间设置为3s,也就是3s后该函数就会停止运行。前面写过一篇博客介绍如何自定义一个装饰器,感兴趣的读者可以自行阅读。在上述的用例中,为了使得超时时间的定义更加灵活,我们采取了从用户输入获取参数的方案,具体内容参考下一章节的介绍。
通过sys获取timeout参数
在上述用例的装饰器中,我们看到了int(sys.argv[2])这样的一个参数,这个参数的意思是用户输入命令行的第三个用空格隔开的参数。举例子说,如果用户执行了python3 test.py -t 1,那么这里就会产生三个输入参数:argv[0]就是test.py,argv[1]就是-t,argv[2]就是1,是一个数组的格式。需要注意的是,argv数组的每一个元素都是字符串格式,如果需要使用数字需要先进行格式转换。这里针对于超时任务的处理,我们指定的执行策略为类似python3 task.py --timeout 5的格式,--timeout后面的数字表示任务执行超时的秒数。如果输入变量格式不正确,或者不满足3个以上的变量输入要求,或者第二个参数不是--timeout,都有可能运行报错。
异常捕获
在定义好超时任务之后,如果达到了设定好的超时时间,系统会给出timeout_decorator.timeout_decorator.TimeoutError报错并结束程序运行。但是我们这里配置超时任务的目的其实是希望在超时任务的函数到达指定时间之后退出,但是不影响其他模块程序的运行,因此这里我们需要对程序给出的报错进行异常捕获,并且通报与抑制该异常。比较简单的方案就是采用except Exception as e的方式,一般Exception最好可以指向指定的报错类型,而不是通用的Exception处理,这有可能带来其他的一些风险。
用例测试
以下按照输入参数的不同,我们先划分为几个模块来分析输出结果以及原因。
超时任务为2s
[dechin@dechin-manjaro timeout]$ python3 timeout_test.py --timeout 2
0%| | 0/3 [00:00, ?it/s]>>> 1 seconds passed.
33%|█████████████▋ | 1/3 [00:01<00:03, 1.99s/it]
Timeout Error Catched!
'Timed Out'
Timeout Task Ended!
结果分析:由于我们在程序中给定了一个一共会执行3s的任务,而这里在命令行中我们将超时时间设置为了2s,因此还没执行完程序就抛出并捕获了异常,成功打印了Timeout Task Ended!这一超时任务之外的任务。
超时任务为3s
[dechin@dechin-manjaro timeout]$ python3 timeout_test.py --timeout 3
0%| | 0/3 [00:00, ?it/s]>>> 1 seconds passed.
33%|█████████████▋ | 1/3 [00:01<00:02, 1.00s/it]>>> 2 seconds passed.
67%|███████████████████████████▎ | 2/3 [00:02<00:01, 1.50s/it]
Timeout Error Catched!
'Timed Out'
Timeout Task Ended!
结果分析:由于我们在程序中给定了一个一共会执行3s的任务,虽然在命令行的输入参数中我们给定了3s的执行时间,但是最终程序还是没有执行结束并抛出了异常。这是因为sleep(1)并不是精准的1s,也许是1.0000001但是这超出来的时间也会对最终执行的总时间产生影响,况且还有其他模块程序所导致的overlap,因此最后也没有执行完成。而且从进度条来看,上面一个章节中时间设置为3s的时候,其实也只是完成了33%的任务而不是67%的任务,这也是符合我们的预期的。
超时任务为4s
[dechin@dechin-manjaro timeout]$ python3 timeout_test.py --timeout 4
0%| | 0/3 [00:00, ?it/s]>>> 1 seconds passed.
33%|█████████████▋ | 1/3 [00:01<00:02, 1.00s/it]>>> 2 seconds passed.
67%|███████████████████████████▎ | 2/3 [00:02<00:01, 1.00s/it]>>> 3 seconds passed.
100%|█████████████████████████████████████████| 3/3 [00:03<00:00, 1.00s/it]
Timeout Task Ended!
结果分析:由于我们在程序中给定了一个一共会执行3s的任务,而在参数输入时配置了4s的超时时间,因此最终任务可以顺利执行完成。这里为了验证上面一个小章节中提到的overlap,我们可以尝试使用系统自带的时间测试模块来测试,如果该程序执行完成之后,一共需要多少的时间:
[dechin@dechin-manjaro timeout]$ time python3 timeout_test.py --timeout 4
0%| | 0/3 [00:00, ?it/s]>>> 1 seconds passed.
33%|█████████████▋ | 1/3 [00:01<00:02, 1.00s/it]>>> 2 seconds passed.
67%|███████████████████████████▎ | 2/3 [00:02<00:01, 1.00s/it]>>> 3 seconds passed.
100%|█████████████████████████████████████████| 3/3 [00:03<00:00, 1.00s/it]
Timeout Task Ended!
real 0m3.167s
user 0m0.147s
sys 0m0.017s
这里我们就可以看到,其实额定为3s的任务,执行完成需要约3.2s的实际时间,多出来的时间就是所谓的overlap。
总结概要
函数的超时设置是一个比较小众使用的功能,可以用于任务的暂停(并非截断)等场景,并且配合上面章节提到的异常捕获和参数输入来使用,会使得任务更加优雅且合理。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/timeout.html
作者ID:DechinPhy
更多原著文章请参考:https://www.cnblogs.com/dechinphy/bk