Jawaban:
Anda dapat menggunakan paket sinyal jika Anda menggunakan 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 detik setelah panggilan alarm.alarm(10)
, pawang dipanggil. Ini menimbulkan pengecualian bahwa Anda dapat mencegat dari kode Python biasa.
Modul ini tidak cocok dengan utas (tetapi kemudian, siapa yang melakukannya?)
Perhatikan bahwa karena kami memunculkan pengecualian ketika batas waktu terjadi, mungkin berakhir tertangkap dan diabaikan di dalam fungsi, misalnya salah satu fungsi tersebut:
def loop_forever():
while 1:
print('sec')
try:
time.sleep(10)
except:
continue
signal.alarm
dan yang terkait SIGALRM
tidak tersedia di platform Windows.
signal.signal
--- akankah mereka semua bekerja dengan baik? Tidak akankah setiap signal.signal
panggilan membatalkan panggilan "bersamaan"?
Anda dapat menggunakannya multiprocessing.Process
untuk melakukan hal itu.
Kode
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()
. yang membuat x jumlah Anda dari proses bersamaan berjalan hingga mereka menyelesaikan pekerjaannya, atau jumlah yang ditentukan dalam join(10)
. Jika Anda memiliki I / O pemblokiran untuk 10 proses, gunakan join (10) yang telah Anda tetapkan untuk menunggu semuanya max 10 untuk setiap proses yang sudah dimulai. Gunakan flag daemon seperti contoh ini stackoverflow.com/a/27420072/2480481 . Tentu saja Anda dapat melewati flag daemon=True
langsung ke multiprocessing.Process()
fungsi.
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.
Bagaimana saya memanggil fungsi atau bagaimana saya membungkusnya sehingga jika dibutuhkan lebih dari 5 detik skrip membatalkannya?
Saya memposting intisari yang memecahkan pertanyaan / masalah ini dengan dekorator dan a threading.Timer
. Ini dia dengan gangguan.
Itu diuji dengan Python 2 dan 3. Ini juga harus bekerja di bawah Unix / Linux dan Windows.
Pertama impor. Upaya ini untuk menjaga kode konsisten terlepas dari versi Python:
from __future__ import print_function
import sys
import threading
from time import sleep
try:
import thread
except ImportError:
import _thread as thread
Gunakan kode independen versi:
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
Sekarang kami telah mengimpor fungsionalitas kami dari perpustakaan standar.
exit_after
penghiasSelanjutnya kita membutuhkan fungsi untuk menghentikan main()
dari thread anak:
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
Dan inilah dekorator itu sendiri:
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
Dan inilah penggunaan yang langsung menjawab pertanyaan Anda tentang keluar setelah 5 detik !:
@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')
Demo:
>>> 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
Panggilan fungsi kedua tidak akan selesai, sebagai gantinya proses harus keluar dengan traceback!
KeyboardInterrupt
tidak selalu menghentikan utas tidurPerhatikan bahwa tidur tidak akan selalu terganggu oleh interupsi keyboard, pada Python 2 pada Windows, misalnya:
@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
juga tidak akan mengganggu kode yang berjalan di ekstensi kecuali jika secara eksplisit memeriksa PyErr_CheckSignals()
, lihat Cython, Python dan KeyboardInterrupt diabaikan
Saya akan menghindari tidur thread lebih dari satu detik, dalam hal apapun - itu satu tahun dalam waktu prosesor.
Bagaimana saya memanggil fungsi atau bagaimana saya membungkusnya sehingga jika dibutuhkan lebih dari 5 detik skrip membatalkannya dan melakukan sesuatu yang lain?
Untuk menangkapnya dan melakukan sesuatu yang lain, Anda dapat menangkap KeyboardInterrupt.
>>> 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()
, mengapa saya tidak bisa langsung mengajukan pengecualian?
multiprocessing.connection.Client
ini? - Mencoba menyelesaikan: stackoverflow.com/questions/57817955/…
Saya punya proposal berbeda yang merupakan fungsi murni (dengan API yang sama dengan saran threading) dan tampaknya berfungsi dengan baik (berdasarkan saran di utas ini)
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
. Jauh lebih baik untuk mengatur default ke None
dan, pada baris pertama fungsi, tambahkan kwargs = kwargs or {}
. Args tidak apa-apa karena tupel tidak bisa berubah.
Saya berlari melintasi utas ini ketika mencari panggilan timeout pada unit test. Saya tidak menemukan sesuatu yang sederhana dalam jawaban atau paket pihak ke-3 jadi saya menulis dekorator di bawah ini yang dapat Anda masukkan ke dalam kode:
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
Maka sesederhana ini untuk timeout tes atau fungsi apa pun yang Anda suka:
@timeout(5.0) # if execution takes longer than 5 seconds, raise a TimeoutError
def test_base_regression(self):
...
Exception
bagian dalam func_wrapper dan lakukan pool.close()
setelah tangkapan untuk memastikan utas selalu mati sesudahnya bagaimanapun caranya . Kemudian Anda bisa melempar TimeoutError
atau apa pun yang Anda inginkan. Tampaknya bekerja untuk saya.
RuntimeError: can't start new thread
. Akankah ini masih berfungsi jika saya mengabaikannya atau ada hal lain yang dapat saya lakukan untuk menyiasati hal ini? Terima kasih sebelumnya!
The stopit
paket, ditemukan di pypi, tampaknya untuk menangani timeout dengan baik.
Saya suka @stopit.threading_timeoutable
dekorator, yang menambahkan timeout
parameter ke fungsi yang didekorasi, yang melakukan apa yang Anda harapkan, itu menghentikan fungsi.
Lihat di pypi: https://pypi.python.org/pypi/stopit
Ada banyak saran, tetapi tidak ada yang menggunakan concurrent.futures, yang saya pikir adalah cara yang paling mudah dibaca untuk menangani ini.
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)
Sangat mudah dibaca dan dikelola.
Kami membuat kumpulan, mengirimkan satu proses dan kemudian menunggu hingga 5 detik sebelum menaikkan TimeoutError yang dapat Anda tangkap dan tangani sesuai kebutuhan.
Berasal dari python 3.2+ dan backported ke 2.7 (pip install futures).
Beralih antara utas dan proses semudah mengganti ProcessPoolExecutor
dengan ThreadPoolExecutor
.
Jika Anda ingin menghentikan Proses pada batas waktu saya sarankan melihat ke Pebble .
Hebat, dekorator dekor proyek PyPi , mudah digunakan, dan dapat diandalkan ( https://pypi.org/project/timeout-decorator/ )
instalasi :
pip install timeout-decorator
Penggunaan :
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()
Saya penulis wrapt_timeout_decorator
Sebagian besar solusi yang disajikan di sini bekerja secara diam-diam di Linux pada pandangan pertama - karena kita memiliki fork () dan sinyal () - tetapi pada windows semuanya terlihat sedikit berbeda. Dan ketika datang ke subthreads di Linux, Anda tidak dapat menggunakan Sinyal lagi.
Untuk memunculkan proses di bawah Windows, itu harus dipilih - dan banyak fungsi yang didekorasi atau metode Kelas tidak.
Jadi, Anda perlu menggunakan pickler yang lebih baik seperti dill dan multiprocess (bukan acar dan multiprocessing) - itu sebabnya Anda tidak dapat menggunakan ProcessPoolExecutor (atau hanya dengan fungsi terbatas).
Untuk batas waktu itu sendiri - Anda perlu menentukan apa arti batas waktu - karena pada Windows perlu waktu yang lama (dan tidak dapat ditentukan) untuk menelurkan proses. Ini bisa rumit pada batas waktu singkat. Mari kita asumsikan, proses pemijahan membutuhkan waktu sekitar 0,5 detik (mudah !!!). Jika Anda memberi batas waktu 0,2 detik, apa yang harus terjadi? Haruskah fungsi waktu habis setelah 0,5 + 0,2 detik (jadi biarkan metode berjalan selama 0,2 detik)? Atau haruskah proses yang disebut time out setelah 0,2 detik (dalam kasus itu, fungsi yang didekorasi akan SELALU habis, karena pada waktu itu ia bahkan tidak muncul)?
Dekorator bersarang juga bisa jahat dan Anda tidak dapat menggunakan Sinyal di subthread. Jika Anda ingin membuat dekorator lintas platform yang benar-benar universal, semua ini perlu dipertimbangkan (dan diuji).
Masalah lain adalah memberikan pengecualian kembali ke pemanggil, serta masalah pencatatan (jika digunakan dalam fungsi yang didekorasi - masuk ke file dalam proses lain TIDAK didukung)
Saya mencoba untuk menutupi semua kasus tepi, Anda mungkin melihat ke dalam paket wrapt_timeout_decorator, atau setidaknya menguji solusi Anda sendiri yang terinspirasi oleh unittests yang digunakan di sana.
@Alexis Eggermont - sayangnya saya tidak punya cukup poin untuk berkomentar - mungkin orang lain dapat memberi tahu Anda - saya pikir saya telah memecahkan masalah impor Anda.
timeout-decorator
tidak berfungsi pada sistem windows karena, windows tidak mendukung dengan signal
baik.
Jika Anda menggunakan timeout-decorator di sistem windows Anda akan mendapatkan yang berikut ini
AttributeError: module 'signal' has no attribute 'SIGALRM'
Beberapa menyarankan untuk menggunakan use_signals=False
tetapi tidak berhasil untuk saya.
Penulis @bitranox membuat paket berikut:
pip install https://github.com/bitranox/wrapt-timeout-decorator/archive/master.zip
Contoh kode:
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()
Memberikan pengecualian berikut:
TimeoutError: Function mytest timed out after 5 seconds
from wrapt_timeout_decorator import *
tampaknya membunuh beberapa impor saya yang lain. Misalnya saya dapat ModuleNotFoundError: No module named 'google.appengine'
, tetapi saya tidak mendapatkan kesalahan ini jika saya tidak mengimpor wrapt_timeout_decorator
Kita dapat menggunakan sinyal yang sama. Saya pikir contoh di bawah ini akan berguna untuk Anda. Ini sangat sederhana dibandingkan dengan utas.
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: ...
selalu merupakan ide yang buruk.
#!/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)
Saya memiliki kebutuhan untuk interupsi berjangka waktu nestable (yang SIGALARM tidak dapat melakukan) yang tidak akan diblokir oleh time.sleep (yang pendekatan berbasis thread tidak dapat melakukan). Saya akhirnya menyalin dan memodifikasi sedikit kode dari sini: http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/
Kode itu sendiri:
#!/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()
dan contoh penggunaan:
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.'
Berikut ini sedikit peningkatan pada solusi berbasis thread yang diberikan.
Kode di bawah ini mendukung pengecualian :
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]
Menjalankannya dengan batas waktu 5 detik:
result = timeout(remote_calculate, (myarg,), timeout_duration=5)
runFunctionCatchExceptions()
fungsi Python tertentu memperoleh GIL disebut. Misalnya berikut tidak akan pernah, atau untuk waktu yang sangat lama, kembali jika disebut dalam fungsi: eval(2**9999999999**9999999999)
. Lihat stackoverflow.com/questions/22138190/...