Hết thời gian cho một cuộc gọi chức năng


300

Tôi đang gọi một hàm trong Python mà tôi biết có thể bị đình trệ và buộc tôi phải khởi động lại tập lệnh.

Làm thế nào để tôi gọi hàm hoặc tôi bọc nó vào cái gì để nếu mất hơn 5 giây thì tập lệnh sẽ hủy nó và làm một cái gì khác?

Câu trả lời:


227

Bạn có thể sử dụng gói tín hiệu nếu bạn đang chạy trên UNIX:

In [1]: import signal

# Register an handler for the timeout
In [2]: def handler(signum, frame):
   ...:     print("Forever is over!")
   ...:     raise Exception("end of time")
   ...: 

# This function *may* run for an indetermined time...
In [3]: def loop_forever():
   ...:     import time
   ...:     while 1:
   ...:         print("sec")
   ...:         time.sleep(1)
   ...:         
   ...:         

# Register the signal function handler
In [4]: signal.signal(signal.SIGALRM, handler)
Out[4]: 0

# Define a timeout for your function
In [5]: signal.alarm(10)
Out[5]: 0

In [6]: try:
   ...:     loop_forever()
   ...: except Exception, exc: 
   ...:     print(exc)
   ....: 
sec
sec
sec
sec
sec
sec
sec
sec
Forever is over!
end of time

# Cancel the timer if the function returned before timeout
# (ok, mine won't but yours maybe will :)
In [7]: signal.alarm(0)
Out[7]: 0

10 giây sau cuộc gọi alarm.alarm(10), trình xử lý được gọi. Điều này đặt ra một ngoại lệ mà bạn có thể chặn từ mã Python thông thường.

Mô-đun này không chơi tốt với các luồng (nhưng sau đó, ai làm?)

Lưu ý rằng vì chúng ta đưa ra một ngoại lệ khi hết thời gian chờ, nó có thể bị bắt và bỏ qua bên trong hàm, ví dụ như một hàm như vậy:

def loop_forever():
    while 1:
        print('sec')
        try:
            time.sleep(10)
        except:
            continue

5
Tôi sử dụng Python 2.5.4. Có một lỗi như vậy: TracBack (cuộc gọi gần đây nhất): Tệp "aa.py", dòng 85, trong func signal.signal (signal.SIGALRM, handler) AttributionError: 'module' object không có thuộc tính 'SIGALRM'
flypen

11
@flypen đó là vì signal.alarmvà các liên quan SIGALRMkhông có sẵn trên nền tảng Windows.
Double AA

2
Nếu có nhiều quy trình, và mỗi cuộc gọi signal.signal--- tất cả chúng có hoạt động tốt không? Không phải mỗi signal.signalcuộc gọi sẽ hủy "đồng thời" một cuộc gọi?
Brown

1
Cảnh báo cho những ai muốn sử dụng phần mở rộng C này: Trình xử lý tín hiệu Python sẽ không được gọi cho đến khi hàm C trả lại quyền điều khiển cho trình thông dịch Python. Đối với trường hợp sử dụng này, hãy sử dụng câu trả lời của ATOzTOA: stackoverflow.com/a/14924210/1286628
wkschwartz

13
Tôi thứ hai cảnh báo về chủ đề. signal.alarm chỉ hoạt động trên luồng chính. Tôi đã cố gắng sử dụng điều này trong các khung nhìn Django - ngay lập tức thất bại với verbiage về chủ đề chính.
JL Peyret

154

Bạn có thể sử dụng multiprocessing.Processđể làm chính xác điều đó.

import multiprocessing
import time

# bar
def bar():
    for i in range(100):
        print "Tick"
        time.sleep(1)

if __name__ == '__main__':
    # Start bar as a process
    p = multiprocessing.Process(target=bar)
    p.start()

    # Wait for 10 seconds or until process finishes
    p.join(10)

    # If thread is still active
    if p.is_alive():
        print "running... let's kill it..."

        # Terminate
        p.terminate()
        p.join()

36
Làm thế nào tôi có thể nhận được giá trị trả về của phương thức đích?
bad_keypoint

4
Điều này dường như không hoạt động nếu chức năng được gọi bị kẹt trên khối I / O.
sudo

4
@bad_keypoint Xem câu trả lời này: stackoverflow.com/a/10415215/1384471 Về cơ bản, bạn chuyển một danh sách theo đó bạn đặt câu trả lời vào.
Peter

1
@sudo thì gỡ cái join(). điều đó làm cho số x quy trình con đồng thời của bạn đang chạy cho đến khi chúng hoàn thành công việc của chúng hoặc số lượng được xác định trong join(10). Trong trường hợp bạn có I / O chặn cho 10 quy trình, bằng cách sử dụng phép nối (10), bạn đã đặt chúng để chờ tất cả trong số chúng tối đa 10 cho quy trình MACHI đã bắt đầu. Sử dụng cờ daemon như ví dụ này stackoverflow.com/a/27420072/2480481 . Tất nhiên u có thể truyền cờ daemon=Truetrực tiếp để multiprocessing.Process()hoạt động.
m3nda

2
@ATOzTOA vấn đề với giải pháp này, ít nhất là cho mục đích của tôi, là nó có khả năng không cho phép trẻ em tự dọn dẹp. Từ tài liệu về chức năng chấm dứtterminate() ... Note that exit handlers and finally clauses, etc., will not be executed. Note that descendant processes of the process will not be terminated – they will simply become orphaned.
abalcerek

78

Làm thế nào để tôi gọi hàm hoặc tôi bọc nó vào cái gì để nếu mất hơn 5 giây thì tập lệnh sẽ hủy nó?

Tôi đăng một ý chính mà giải quyết câu hỏi này / vấn đề với một trang trí và một threading.Timer. Đây là một sự cố.

Nhập khẩu và thiết lập để tương thích

Nó đã được thử nghiệm với Python 2 và 3. Nó cũng sẽ hoạt động trong Unix / Linux và Windows.

Đầu tiên là hàng nhập khẩu. Những nỗ lực này để giữ cho mã nhất quán bất kể phiên bản Python:

from __future__ import print_function
import sys
import threading
from time import sleep
try:
    import thread
except ImportError:
    import _thread as thread

Sử dụng mã độc lập phiên bản:

try:
    range, _print = xrange, print
    def print(*args, **kwargs): 
        flush = kwargs.pop('flush', False)
        _print(*args, **kwargs)
        if flush:
            kwargs.get('file', sys.stdout).flush()            
except NameError:
    pass

Bây giờ chúng tôi đã nhập chức năng của chúng tôi từ thư viện tiêu chuẩn.

exit_after người trang trí

Tiếp theo chúng ta cần một hàm để chấm dứt main()từ luồng con:

def quit_function(fn_name):
    # print to stderr, unbuffered in Python 2.
    print('{0} took too long'.format(fn_name), file=sys.stderr)
    sys.stderr.flush() # Python 3 stderr is likely buffered.
    thread.interrupt_main() # raises KeyboardInterrupt

Và đây là trang trí chính nó:

def exit_after(s):
    '''
    use as decorator to exit process if 
    function takes longer than s seconds
    '''
    def outer(fn):
        def inner(*args, **kwargs):
            timer = threading.Timer(s, quit_function, args=[fn.__name__])
            timer.start()
            try:
                result = fn(*args, **kwargs)
            finally:
                timer.cancel()
            return result
        return inner
    return outer

Sử dụng

Và đây là cách sử dụng trả lời trực tiếp câu hỏi của bạn về việc thoát sau 5 giây!:

@exit_after(5)
def countdown(n):
    print('countdown started', flush=True)
    for i in range(n, -1, -1):
        print(i, end=', ', flush=True)
        sleep(1)
    print('countdown finished')

Bản giới thiệu:

>>> countdown(3)
countdown started
3, 2, 1, 0, countdown finished
>>> countdown(10)
countdown started
10, 9, 8, 7, 6, countdown took too long
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner
  File "<stdin>", line 6, in countdown
KeyboardInterrupt

Cuộc gọi chức năng thứ hai sẽ không kết thúc, thay vào đó quá trình sẽ thoát với dấu vết!

KeyboardInterrupt không phải lúc nào cũng dừng một sợi ngủ

Lưu ý rằng giấc ngủ sẽ không luôn bị gián đoạn bởi một ngắt bàn phím, trên Python 2 trên Windows, ví dụ:

@exit_after(1)
def sleep10():
    sleep(10)
    print('slept 10 seconds')

>>> sleep10()
sleep10 took too long         # Note that it hangs here about 9 more seconds
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner
  File "<stdin>", line 3, in sleep10
KeyboardInterrupt

Nó cũng không có khả năng làm gián đoạn mã đang chạy trong các tiện ích mở rộng trừ khi nó kiểm tra rõ ràng PyErr_CheckSignals(), xem Cython, Python và KeyboardInterrupt bị bỏ qua

Tôi sẽ tránh ngủ một luồng nhiều hơn một giây, trong mọi trường hợp - đó là một thời gian xử lý.

Làm thế nào để tôi gọi hàm hoặc tôi bọc nó vào cái gì để nếu mất hơn 5 giây thì tập lệnh sẽ hủy nó và làm một cái gì khác?

Để bắt nó và làm một cái gì đó khác, bạn có thể bắt Bàn phím tắt.

>>> try:
...     countdown(10)
... except KeyboardInterrupt:
...     print('do something else')
... 
countdown started
10, 9, 8, 7, 6, countdown took too long
do something else

Tôi chưa đọc toàn bộ bài viết của bạn, nhưng tôi chỉ tự hỏi: nếu tuôn ra là 0 thì sao? Điều đó sẽ được hiểu là Sai trong câu lệnh if bên dưới, phải không?
Koenraad van Duin

2
Tại sao tôi phải gọi thread.interrupt_main(), tại sao tôi không thể trực tiếp đưa ra một ngoại lệ?
Anirban Nag 'tintinmj'

Bất kỳ suy nghĩ về gói multiprocessing.connection.Clientvới điều này? - Đang cố gắng giải quyết: stackoverflow.com/questions/57817955/ từ
wwii

51

Tôi có một đề xuất khác là một hàm thuần túy (có cùng API với đề xuất luồng) và dường như hoạt động tốt (dựa trên các đề xuất về luồng này)

def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
    import signal

    class TimeoutError(Exception):
        pass

    def handler(signum, frame):
        raise TimeoutError()

    # set the timeout handler
    signal.signal(signal.SIGALRM, handler) 
    signal.alarm(timeout_duration)
    try:
        result = func(*args, **kwargs)
    except TimeoutError as exc:
        result = default
    finally:
        signal.alarm(0)

    return result

3
Bạn cũng nên khôi phục trình xử lý tín hiệu gốc. Xem stackoverflow.com/questions/492519/
Mạnh

9
Thêm một lưu ý: Phương thức tín hiệu Unix chỉ hoạt động nếu bạn đang áp dụng nó trong luồng chính. Áp dụng nó trong một tiểu chủ đề ném một ngoại lệ và sẽ không hoạt động.
Martin Konecny

12
Đây không phải là giải pháp tốt nhất vì nó chỉ hoạt động trên linux.
tối đa

17
Tối đa, không đúng sự thật - hoạt động trên mọi unix tuân thủ POSIX. Tôi nghĩ bình luận của bạn nên chính xác hơn, không hoạt động trên Windows.
Chris Johnson

6
Bạn nên tránh đặt kwargs thành một lệnh trống. Một gotcha Python phổ biến là các đối số mặc định trên các hàm có thể thay đổi. Vì vậy, từ điển sẽ được chia sẻ trên tất cả các cuộc gọi đến timeout. Sẽ tốt hơn nhiều nếu đặt mặc định thành Nonevà, trên dòng đầu tiên của hàm, thêm kwargs = kwargs or {}. Args là ổn bởi vì bộ dữ liệu không thể thay đổi.
scottmrogowski

32

Tôi đã chạy qua chủ đề này khi tìm kiếm một cuộc gọi hết thời gian trên các bài kiểm tra đơn vị. Tôi không tìm thấy bất cứ điều gì đơn giản trong các câu trả lời hoặc các gói của bên thứ 3 vì vậy tôi đã viết trình trang trí bên dưới để bạn có thể thả ngay vào mã:

import multiprocessing.pool
import functools

def timeout(max_timeout):
    """Timeout decorator, parameter in seconds."""
    def timeout_decorator(item):
        """Wrap the original function."""
        @functools.wraps(item)
        def func_wrapper(*args, **kwargs):
            """Closure for function."""
            pool = multiprocessing.pool.ThreadPool(processes=1)
            async_result = pool.apply_async(item, args, kwargs)
            # raises a TimeoutError if execution exceeds max_timeout
            return async_result.get(max_timeout)
        return func_wrapper
    return timeout_decorator

Sau đó, nó đơn giản như thế này để hết thời gian kiểm tra hoặc bất kỳ chức năng nào bạn thích:

@timeout(5.0)  # if execution takes longer than 5 seconds, raise a TimeoutError
def test_base_regression(self):
    ...

14
Hãy cẩn thận vì điều này không chấm dứt chức năng sau khi hết thời gian chờ!
Sylvain

Lưu ý rằng trên Windows, điều này tạo ra một quy trình hoàn toàn mới - sẽ ăn vào thời gian chờ, có lẽ rất nhiều nếu các phụ thuộc mất nhiều thời gian để thiết lập.
Aaron Hall

1
Vâng, điều này cần một số điều chỉnh. Nó để lại chủ đề đi mãi mãi.
sudo

2
IDK nếu đây là cách tốt nhất, nhưng bạn có thể thử / bắt Exceptionbên trong func_wrapper và thực hiện pool.close()sau khi bắt để đảm bảo luồng luôn chết sau đó bất kể là gì. Sau đó, bạn có thể ném TimeoutErrorhoặc bất cứ điều gì bạn muốn sau. Dường như làm việc cho tôi.
sudo

2
Điều này là hữu ích, nhưng một khi tôi đã thực hiện nó nhiều lần, tôi nhận được RuntimeError: can't start new thread. Nó sẽ vẫn hoạt động nếu tôi bỏ qua nó hoặc tôi có thể làm gì khác để khắc phục điều này? Cảm ơn trước!
Benjie

20

Các stopitgói, được tìm thấy trên pypi, dường như để xử lý timeouts tốt.

Tôi thích trình @stopit.threading_timeoutabletrang trí, trong đó thêm một timeouttham số cho chức năng trang trí, làm những gì bạn mong đợi, nó dừng chức năng.

Hãy xem thử trên pypi: https://pypi.python.org/pypi/stopit


1
Nó rất tiện dụng và an toàn cho chủ đề! Cảm ơn và cộng một! Đây là lựa chọn tốt nhất tôi tìm thấy cho đến nay và thậm chí còn tốt hơn câu trả lời được chấp nhận !!
Yahya

Thư viện tuyên bố, một số chức năng không hoạt động trong Windows.
Stefan Simik

16

Có rất nhiều gợi ý, nhưng không có gợi ý nào sử dụng conc conc.futures, mà tôi nghĩ là cách dễ đọc nhất để xử lý việc này.

from concurrent.futures import ProcessPoolExecutor

# Warning: this does not terminate function if timeout
def timeout_five(fnc, *args, **kwargs):
    with ProcessPoolExecutor() as p:
        f = p.submit(fnc, *args, **kwargs)
        return f.result(timeout=5)

Siêu đơn giản để đọc và duy trì.

Chúng tôi tạo một nhóm, gửi một quy trình duy nhất và sau đó đợi tối đa 5 giây trước khi nâng TimeoutError mà bạn có thể bắt và xử lý theo cách bạn cần.

Có nguồn gốc từ python 3.2+ và được nhập vào 2.7 (tương lai cài đặt pip).

Chuyển đổi giữa các luồng và các quy trình đơn giản như thay thế ProcessPoolExecutorbằng ThreadPoolExecutor.

Nếu bạn muốn chấm dứt Quá trình hết thời gian, tôi khuyên bạn nên xem xét Pebble .


2
"Cảnh báo: điều này không chấm dứt chức năng nếu hết thời gian" nghĩa là gì?
Scott Stafford

5
@ScottStafford Các quy trình / chủ đề không kết thúc chỉ vì TimeoutError đã được nâng lên. Vì vậy, quá trình hoặc luồng vẫn sẽ cố gắng chạy để hoàn thành và sẽ không tự động cung cấp cho bạn quyền kiểm soát khi hết thời gian.
Brian

Điều này sẽ cho phép tôi lưu bất kỳ kết quả là trung gian tại thời điểm đó? ví dụ: nếu tôi có hàm đệ quy mà tôi đặt thời gian chờ là 5 và trong thời gian đó tôi có kết quả một phần, làm thế nào để tôi viết hàm để trả về kết quả một phần khi hết thời gian?
SumNeuron

Tôi đang sử dụng chính xác điều này, tuy nhiên tôi có 1000 nhiệm vụ, mỗi nhiệm vụ được cho phép 5 giây trước khi hết thời gian. Vấn đề của tôi là các lõi bị tắc nghẽn trong các tác vụ không bao giờ kết thúc vì thời gian chờ chỉ được áp dụng trên tổng số các tác vụ không phải trên các tác vụ riêng lẻ. concallel.futures không cung cấp giải pháp cho afaik này.
Bastiaan

12

Công cụ trang trí thời gian chờ dự án PyPi tuyệt vời, dễ sử dụng và đáng tin cậy ( https://pypi.org/project/timeout-decorator/ )

cài đặt :

pip install timeout-decorator

Cách sử dụng :

import time
import timeout_decorator

@timeout_decorator.timeout(5)
def mytest():
    print "Start"
    for i in range(1,10):
        time.sleep(1)
        print "%d seconds have passed" % i

if __name__ == '__main__':
    mytest()

2
Tôi đánh giá cao giải pháp rõ ràng. Nhưng bất cứ ai cũng có thể giải thích cách thư viện này hoạt động, đặc biệt là khi xử lý đa luồng. Cá nhân tôi sợ sử dụng một máy móc không xác định để xử lý các chủ đề hoặc tín hiệu.
wsysuper

@wsysuper lib có 2 chế độ hoạt động: mở luồng mới hoặc quy trình con mới (giả sử là luồng an toàn)
Gil

Điều này làm việc rất tốt cho tôi!
Florian Heigl

6

Tôi là tác giả của Wrapt_timeout_decorator

Hầu hết các giải pháp được trình bày ở đây hoạt động một cách khôn ngoan trong Linux ngay từ cái nhìn đầu tiên - bởi vì chúng tôi có fork () và tín hiệu () - nhưng trên các cửa sổ, mọi thứ trông hơi khác. Và khi nói đến subthreads trên Linux, Bạn không thể sử dụng Tín hiệu nữa.

Để tạo ra một quy trình trong Windows, nó cần phải được chọn - và nhiều hàm được trang trí hoặc các phương thức Class thì không.

Vì vậy, bạn cần sử dụng một công cụ chọn tốt hơn như thì là và đa xử lý (không phải là xử lý và đa xử lý) - đó là lý do tại sao bạn không thể sử dụng ProcessPoolExecutor (hoặc chỉ với chức năng giới hạn).

Đối với thời gian chờ chính nó - Bạn cần xác định thời gian chờ nghĩa là gì - bởi vì trên Windows, sẽ mất thời gian đáng kể (và không thể xác định) để sinh ra quá trình. Điều này có thể là khó khăn trong thời gian chờ ngắn. Giả sử, sinh sản quá trình mất khoảng 0,5 giây (dễ dàng !!!). Nếu bạn cho thời gian chờ là 0,2 giây thì chuyện gì sẽ xảy ra? Có nên hết thời gian sau 0,5 + 0,2 giây (vì vậy hãy để phương thức chạy trong 0,2 giây)? Hoặc có nên hết thời gian xử lý sau 0,2 giây (trong trường hợp đó, chức năng được trang trí sẽ LUÔN hết thời gian chờ, vì trong thời gian đó nó thậm chí không được sinh ra)?

Ngoài ra các trang trí lồng nhau có thể khó chịu và Bạn không thể sử dụng Tín hiệu trong một subthread. Nếu bạn muốn tạo ra một trang trí đa nền tảng thực sự phổ quát, tất cả điều này cần phải được xem xét (và thử nghiệm).

Các vấn đề khác đang chuyển ngoại lệ trở lại cho người gọi, cũng như các vấn đề đăng nhập (nếu được sử dụng trong chức năng được trang trí - đăng nhập vào các tệp trong quy trình khác KHÔNG được hỗ trợ)

Tôi đã cố gắng bao gồm tất cả các trường hợp cạnh, Bạn có thể xem xét gói Wrapt_timeout_decorator hoặc ít nhất là kiểm tra các giải pháp của riêng bạn lấy cảm hứng từ những điều không mong muốn được sử dụng ở đó.

@Alexis Eggermont - thật không may, tôi không có đủ điểm để nhận xét - có thể người khác có thể thông báo cho Bạn - Tôi nghĩ rằng tôi đã giải quyết vấn đề nhập khẩu của bạn.


3

timeout-decoratorkhông hoạt động trên hệ thống windows vì windows không hỗ trợ signaltốt.

Nếu bạn sử dụng thời gian chờ trang trí trong hệ thống cửa sổ, bạn sẽ nhận được những điều sau đây

AttributeError: module 'signal' has no attribute 'SIGALRM'

Một số đề nghị sử dụng use_signals=Falsenhưng không làm việc cho tôi.

Tác giả @bitranox đã tạo gói sau:

pip install https://github.com/bitranox/wrapt-timeout-decorator/archive/master.zip

Mẫu mã:

import time
from wrapt_timeout_decorator import *

@timeout(5)
def mytest(message):
    print(message)
    for i in range(1,10):
        time.sleep(1)
        print('{} seconds have passed'.format(i))

def main():
    mytest('starting')


if __name__ == '__main__':
    main()

Đưa ra ngoại lệ sau:

TimeoutError: Function mytest timed out after 5 seconds

Âm thanh này giống như một giải pháp rất tốt đẹp. Kỳ lạ thay, dòng from wrapt_timeout_decorator import * dường như giết chết một số hàng nhập khẩu khác của tôi. Ví dụ tôi nhận được ModuleNotFoundError: No module named 'google.appengine', nhưng tôi không gặp phải lỗi này nếu tôi không nhập Wrapt_timeout_decorator
Alexis Eggermont

@AlexisEggermont Tôi sắp sử dụng ứng dụng này với appengine ... vì vậy tôi rất tò mò liệu lỗi này có còn tồn tại không?
PascalVKooten

2

Chúng ta có thể sử dụng tín hiệu cho cùng. Tôi nghĩ ví dụ dưới đây sẽ hữu ích cho bạn. Nó rất đơn giản so với chủ đề.

import signal

def timeout(signum, frame):
    raise myException

#this is an infinite loop, never ending under normal circumstances
def main():
    print 'Starting Main ',
    while 1:
        print 'in main ',

#SIGALRM is only usable on a unix platform
signal.signal(signal.SIGALRM, timeout)

#change 5 to however many seconds you need
signal.alarm(5)

try:
    main()
except myException:
    print "whoops"

1
Sẽ tốt hơn nếu chọn một ngoại lệ cụ thể và chỉ bắt nó. Bare try: ... except: ...luôn là một ý tưởng tồi.
hivert

Tôi đồng ý với bạn hivert.
AR

trong khi tôi hiểu lý do, với tư cách là một sysadmin / nhà tích hợp tôi không đồng ý - mã python nổi tiếng là bỏ qua việc xử lý lỗi và xử lý một điều bạn mong đợi không đủ tốt cho phần mềm chất lượng. bạn có thể xử lý 5 điều bạn dự định VÀ một chiến lược chung cho những thứ khác. "Trac trở lại, Không" không phải là một chiến lược, đó là một sự xúc phạm.
Florian Heigl

2
#!/usr/bin/python2
import sys, subprocess, threading
proc = subprocess.Popen(sys.argv[2:])
timer = threading.Timer(float(sys.argv[1]), proc.terminate)
timer.start()
proc.wait()
timer.cancel()
exit(proc.returncode)

7
Mặc dù mã này có thể trả lời câu hỏi, cung cấp ngữ cảnh bổ sung về cách thức và / hoặc lý do giải quyết vấn đề sẽ cải thiện giá trị lâu dài của câu trả lời
Dan Cornilescu

1

Tôi đã có một nhu cầu cho nestable ngắt theo thời gian (mà SIGALARM không thể làm) mà không bị che chắn bởi time.sleep (mà cách tiếp cận dựa trên thread không thể làm). Tôi đã kết thúc việc sao chép và sửa đổi mã nhẹ từ đây: http://code.activestate.com/recipes/577600-queue-for-managing-multipl-sigalrm-alarms-concurr/

Bản thân mã:

#!/usr/bin/python

# lightly modified version of http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/


"""alarm.py: Permits multiple SIGALRM events to be queued.

Uses a `heapq` to store the objects to be called when an alarm signal is
raised, so that the next alarm is always at the top of the heap.
"""

import heapq
import signal
from time import time

__version__ = '$Revision: 2539 $'.split()[1]

alarmlist = []

__new_alarm = lambda t, f, a, k: (t + time(), f, a, k)
__next_alarm = lambda: int(round(alarmlist[0][0] - time())) if alarmlist else None
__set_alarm = lambda: signal.alarm(max(__next_alarm(), 1))


class TimeoutError(Exception):
    def __init__(self, message, id_=None):
        self.message = message
        self.id_ = id_


class Timeout:
    ''' id_ allows for nested timeouts. '''
    def __init__(self, id_=None, seconds=1, error_message='Timeout'):
        self.seconds = seconds
        self.error_message = error_message
        self.id_ = id_
    def handle_timeout(self):
        raise TimeoutError(self.error_message, self.id_)
    def __enter__(self):
        self.this_alarm = alarm(self.seconds, self.handle_timeout)
    def __exit__(self, type, value, traceback):
        try:
            cancel(self.this_alarm) 
        except ValueError:
            pass


def __clear_alarm():
    """Clear an existing alarm.

    If the alarm signal was set to a callable other than our own, queue the
    previous alarm settings.
    """
    oldsec = signal.alarm(0)
    oldfunc = signal.signal(signal.SIGALRM, __alarm_handler)
    if oldsec > 0 and oldfunc != __alarm_handler:
        heapq.heappush(alarmlist, (__new_alarm(oldsec, oldfunc, [], {})))


def __alarm_handler(*zargs):
    """Handle an alarm by calling any due heap entries and resetting the alarm.

    Note that multiple heap entries might get called, especially if calling an
    entry takes a lot of time.
    """
    try:
        nextt = __next_alarm()
        while nextt is not None and nextt <= 0:
            (tm, func, args, keys) = heapq.heappop(alarmlist)
            func(*args, **keys)
            nextt = __next_alarm()
    finally:
        if alarmlist: __set_alarm()


def alarm(sec, func, *args, **keys):
    """Set an alarm.

    When the alarm is raised in `sec` seconds, the handler will call `func`,
    passing `args` and `keys`. Return the heap entry (which is just a big
    tuple), so that it can be cancelled by calling `cancel()`.
    """
    __clear_alarm()
    try:
        newalarm = __new_alarm(sec, func, args, keys)
        heapq.heappush(alarmlist, newalarm)
        return newalarm
    finally:
        __set_alarm()


def cancel(alarm):
    """Cancel an alarm by passing the heap entry returned by `alarm()`.

    It is an error to try to cancel an alarm which has already occurred.
    """
    __clear_alarm()
    try:
        alarmlist.remove(alarm)
        heapq.heapify(alarmlist)
    finally:
        if alarmlist: __set_alarm()

và một ví dụ sử dụng:

import alarm
from time import sleep

try:
    with alarm.Timeout(id_='a', seconds=5):
        try:
            with alarm.Timeout(id_='b', seconds=2):
                sleep(3)
        except alarm.TimeoutError as e:
            print 'raised', e.id_
        sleep(30)
except alarm.TimeoutError as e:
    print 'raised', e.id_
else:
    print 'nope.'

Điều này cũng sử dụng tín hiệu do đó sẽ không hoạt động nếu được gọi từ một luồng.
garg10

0

Đây là một cải tiến nhỏ cho giải pháp dựa trên chủ đề đã cho.

Mã dưới đây hỗ trợ các ngoại lệ :

def runFunctionCatchExceptions(func, *args, **kwargs):
    try:
        result = func(*args, **kwargs)
    except Exception, message:
        return ["exception", message]

    return ["RESULT", result]


def runFunctionWithTimeout(func, args=(), kwargs={}, timeout_duration=10, default=None):
    import threading
    class InterruptableThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
            self.result = default
        def run(self):
            self.result = runFunctionCatchExceptions(func, *args, **kwargs)
    it = InterruptableThread()
    it.start()
    it.join(timeout_duration)
    if it.isAlive():
        return default

    if it.result[0] == "exception":
        raise it.result[1]

    return it.result[1]

Gọi nó với thời gian chờ 5 giây:

result = timeout(remote_calculate, (myarg,), timeout_duration=5)

1
Điều này sẽ đưa ra một ngoại lệ mới ẩn dấu vết ban đầu. Xem phiên bản của tôi dưới đây ...
Meitham

1
Điều này cũng không an toàn, như thể trong runFunctionCatchExceptions()các hàm Python nhất định có được GIL được gọi. Ví dụ, sau đây sẽ không bao giờ, hoặc trong một thời gian rất dài, trả về nếu được gọi trong hàm : eval(2**9999999999**9999999999). Xem stackoverflow.com/questions/22138190/
Mạnh
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.