Câu trả lời:
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
signal.alarm
và các liên quan SIGALRM
không có sẵn trên nền tảng Windows.
signal.signal
--- tất cả chúng có hoạt động tốt không? Không phải mỗi signal.signal
cuộc gọi sẽ hủy "đồng thời" một cuộc gọi?
Bạn có thể sử dụng multiprocessing.Process
để làm chính xác điều đó.
Mã
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()
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=True
trực tiếp để multiprocessing.Process()
hoạt động.
terminate() ... 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.
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ố.
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
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
thread.interrupt_main()
, tại sao tôi không thể trực tiếp đưa ra một ngoại lệ?
multiprocessing.connection.Client
với điều này? - Đang cố gắng giải quyết: stackoverflow.com/questions/57817955/ từ
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
timeout
. Sẽ tốt hơn nhiều nếu đặt mặc định thành None
và, 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.
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):
...
Exception
bê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 TimeoutError
hoặc bất cứ điều gì bạn muốn sau. Dường như làm việc cho tôi.
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!
Các stopit
gó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_timeoutable
trang trí, trong đó thêm một timeout
tham 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
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ế ProcessPoolExecutor
bằ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 .
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()
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.
timeout-decorator
không hoạt động trên hệ thống windows vì windows không hỗ trợ signal
tố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=False
như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
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
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"
try: ... except: ...
luôn là một ý tưởng tồi.
#!/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)
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.'
Đâ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)
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/