超时功能,如果它需要太长时间才能完成

超时功能,如果它需要太长时间才能完成

问题描述:

我有一个shell脚本,通过包含URL的文本文件进行循环,该文件包含我想要访问并截取的URL。超时功能,如果它需要太长时间才能完成

所有这一切都很简单。该脚本初始化一个类,该类在运行时创建列表中每个站点的屏幕截图。有些网站需要很长时间才能加载,有些网站可能根本无法加载。所以我想在一个超时脚本中包装screengrabber函数,如果函数在10秒内无法完成,则返回False

我满足于最简单的解决方案,也许设置一个异步计时器,在10秒后返回False,而不管函数内部发生了什么?

+0

对于所有懒惰的人,谁爱用的库而不是复制粘贴+代码片段来自*的:https://pypi.python.org/pypi/timeout-decorator – guettli 2017-05-02 12:42:41

signal的文档中描述了超时操作的过程。

其基本思想是使用信号处理程序在某个时间间隔内设置警报,并在该定时器到期后引发异常。

请注意,这只适用于UNIX。

以下是创建装饰器的实现(将以下代码保存为timeout.py)。

from functools import wraps 
import errno 
import os 
import signal 

class TimeoutError(Exception): 
    pass 

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)): 
    def decorator(func): 
     def _handle_timeout(signum, frame): 
      raise TimeoutError(error_message) 

     def wrapper(*args, **kwargs): 
      signal.signal(signal.SIGALRM, _handle_timeout) 
      signal.alarm(seconds) 
      try: 
       result = func(*args, **kwargs) 
      finally: 
       signal.alarm(0) 
      return result 

     return wraps(func)(wrapper) 

    return decorator 

这创建了一个名为@timeout的装饰器,可以应用于任何长时间运行的函数。

因此,在应用程序代码,您可以使用像这样的装饰:

from timeout import timeout 

# Timeout a long running function with the default expiry of 10 seconds. 
@timeout 
def long_running_function1(): 
    ... 

# Timeout after 5 seconds 
@timeout(5) 
def long_running_function2(): 
    ... 

# Timeout after 30 seconds, with the error "Connection timed out" 
@timeout(30, os.strerror(errno.ETIMEDOUT)) 
def long_running_function3(): 
    ... 
+50

请注意,这不是线程安全的:如果您使用多线程,信号将被随机线程捕获。对于单线程程序,这是最简单的解决方案。 – Wim 2010-02-17 17:03:11

+1

不错。另外,建议使用@ functools.wrap(func)' – shx2 2013-10-31 19:58:05

+6

FYI修饰函数'wrapper',在第一个“@timeout”之后会丢失parens。它应该读取'@timeout()def ...'。 – 2014-03-16 22:28:04

我重写使用with声明大卫的回答,它可以让你做这样做:

with timeout(seconds=3): 
    time.sleep(4) 

哪会引发TimeoutError。

的代码仍然使用signal,因此仅适用于UNIX:

import signal 

class timeout: 
    def __init__(self, seconds=1, error_message='Timeout'): 
     self.seconds = seconds 
     self.error_message = error_message 
    def handle_timeout(self, signum, frame): 
     raise TimeoutError(self.error_message) 
    def __enter__(self): 
     signal.signal(signal.SIGALRM, self.handle_timeout) 
     signal.alarm(self.seconds) 
    def __exit__(self, type, value, traceback): 
     signal.alarm(0) 
+6

Python Framester 2014-10-02 09:17:48

+2

你可以很容易地添加一个装饰器'@ timeout.timeout'作为静态方法。然后,您可以轻松地在装饰器或“with”语句之间进行选择。 – Kevin 2015-10-15 18:09:58

+5

有趣的是,如果在'Timeout(t)'上下文中引发任何错误,'__exit__'仍然会被调用,从而避免由引发'TimeOutError'而不是真正的错误引起的任何复杂性。这是一个非常可爱的解决方案。 – lucastamoios 2016-10-30 13:38:03