Bir işlev çağrısında zaman aşımı


301

Python'da durdurabileceğim ve betiği yeniden başlatmam için beni zorlayabilecek bir işlev arıyorum.

İşlevi nasıl çağırırım veya 5 saniyeden uzun sürerse komut dosyasının iptal etmesi ve başka bir şey yapması için ne sararım?

Yanıtlar:


227

UNIX üzerinde çalışıyorsanız sinyal paketini kullanabilirsiniz :

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

Aramadan 10 saniye sonra alarm.alarm(10)işleyici çağrılır. Bu, normal Python kodundan kesebileceğiniz bir istisna oluşturur.

Bu modül iş parçacığı ile iyi oynamıyor (ama sonra kim oynuyor?)

Not o zaman aşımı meydana geldiğinde bir istisna yükseltmek zamandan beri, böyle bir fonksiyonun örneğin yakalanarak işlevi içinde göz ardı sona erebilir:

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

5
Python 2.5.4 kullanıyorum. Böyle bir hata var: Geri izleme (en son çağrı son): func signal.signal (sinyal.SIGALRM, işleyici) dosyasında "aa.py" dosyası, 85. dosya - AttributeError: 'module' nesnesinin 'SIGALRM' özelliği yok
flypen

11
@flypen bunun nedeni signal.alarmve ilgili SIGALRMWindows platformlarında mevcut değildir.
Çift AA

2
Çok fazla işlem varsa ve her çağrı signal.signal--- hepsi düzgün çalışır mı? Her signal.signalçağrı "eşzamanlı" bir çağrıyı iptal etmeyecek mi?
brownian

1
Bunu bir C uzantısıyla kullanmak isteyenler için uyarı: C işlevi kontrolü Python yorumlayıcısına döndürene kadar Python sinyal işleyicisi çağrılmaz. Bu kullanım durumu için ATOzTOA'nın yanıtını kullanın: stackoverflow.com/a/14924210/1286628
wkschwartz

13
Konu ile ilgili uyarıyı ikinci olarak belirttim. signal.alarm sadece ana iş parçacığında çalışır. Bu Django görünümlerinde kullanmaya çalıştım - hemen sadece ana iş parçacığı hakkında verbiage ile başarısız.
JL Peyret

154

multiprocessing.ProcessTam olarak bunu yapmak için kullanabilirsiniz .

kod

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
Hedef yöntemin dönüş değerini nasıl alabilirim?
bad_keypoints

4
Aranan işlev bir G / Ç bloğuna takılırsa bu işe yaramaz.
sudo

4
@bad_keypoints Şu yanıta bakın: stackoverflow.com/a/10415215/1384471 Temel olarak, yanıtı koyduğunuz bir liste iletirsiniz.
Peter

1
@sudo ardından kaldırın join(). x eşzamanlı alt işleminizin çalışmasını bitirinceye veya çalışmakta olan miktara kadar çalışmasını sağlar join(10). 10 işlem için engelleme G / Ç'niz olması durumunda, birleştirme (10) kullanarak, bunların tümünü başlayan her bir işlem için en fazla 10 beklemesini ayarladınız. Bu örnek gibi daemon bayrağını kullanın stackoverflow.com/a/27420072/2480481 . Tabii ki u fonksiyon daemon=Truedoğrudan bayrak geçirebilirsiniz multiprocessing.Process().
m3nda

2
@ATOzTOA bu çözümle ilgili sorun, en azından benim amacım için, potansiyel olarak çocuk basamaklarının kendilerinden sonra temizlenmesine izin vermemesidir. Sonlandırma fonksiyonunun dokümantasyonundanterminate() ... 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

İşlevi nasıl arayabilirim veya 5 saniyeden uzun sürerse komut dosyasının iptal etmesi için onu ne içine sarabilirim?

Ben yayınlanmıştır özünü o çözer bir dekoratör ve bu soru / sorun threading.Timer. Burada bir arıza var.

Uyumluluk için içe aktarma ve kurulumlar

Python 2 ve 3 ile test edildi. Ayrıca Unix / Linux ve Windows altında da çalışmalıdır.

İlk olarak ithalat. Bunlar Python sürümüne bakılmaksızın kodu tutarlı tutmaya çalışır:

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

Sürümden bağımsız kod kullanı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

Şimdi işlevselliğimizi standart kütüphaneden içe aktardık.

exit_after dekoratör

Sonra main()alt iş parçacığından sonlandırmak için bir işleve ihtiyacımız var :

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

Ve işte dekoratörün kendisi:

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

kullanım

Ve işte 5 saniye sonra çıkma ile ilgili sorunuza doğrudan cevap veren kullanım !:

@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

İkinci işlev çağrısı bitmez, bunun yerine işlem bir geri izleme ile çıkmalıdır!

KeyboardInterrupt uyku ipliğini her zaman durdurmaz

Uyku durumunun Windows'ta Python 2'de klavye kesintisi ile her zaman kesintiye uğramayacağını unutmayın, örn:

@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

ne de açıkça kontrol etmedikçe uzantılarda çalışan kodu kesmesi olası değildir PyErr_CheckSignals(), bkz. Cython, Python ve KeyboardInterrupt yok sayıldı

Her durumda, bir iplik bir saniyeden fazla uyumaktan kaçınırım - bu işlemci zamanında bir eon.

İşlevi nasıl çağırırım veya 5 saniyeden uzun sürerse komut dosyasının iptal etmesi ve başka bir şey yapması için ne sararım?

Yakalamak ve başka bir şey yapmak için KeyboardInterrupt'ı yakalayabilirsiniz.

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

Henüz tüm yazınızı okumadım, ama merak ettim: ya floş 0 ise? Bu, altındaki if ifadesinde Yanlış olarak yorumlanır, değil mi?
Koenraad van Duin

3
Neden aramam gerekiyor thread.interrupt_main(), neden doğrudan bir istisna oluşturamıyorum?
Anirban Nag 'tintinmj'

Bununla sarma hakkında herhangi bir düşünce var multiprocessing.connection.Clientmı? - Çözmeye çalışıyorum: stackoverflow.com/questions/57817955/…
İkinci

51

Saf bir işlevi olan (iş parçacığı öneri ile aynı API ile) ve (bu iş parçacığı önerileri dayalı) iyi çalışıyor gibi görünüyor farklı bir teklif var

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
Orijinal sinyal tutucuyu da geri yüklemelisiniz. Bkz. Stackoverflow.com/questions/492519/…
Martin Konecny

9
Bir not daha: Unix sinyal yöntemi yalnızca ana iş parçacığına uyguladığınızda çalışır. Bir alt iş parçacığına uygulamak bir istisna atar ve çalışmaz.
Martin Konecny

12
Bu en iyi çözüm değildir çünkü sadece linux üzerinde çalışır.
max

17
Maks, doğru değil - POSIX uyumlu herhangi bir unix üzerinde çalışır. Bence yorumunuz daha doğru olmalı, Windows'da çalışmıyor.
Chris Johnson

6
Kwargları boş bir dikteye ayarlamaktan kaçınmalısınız. Yaygın bir Python gotcha, işlevler üzerindeki varsayılan argümanların değiştirilebilir olmasıdır. Böylece bu sözlük tüm çağrılarda paylaşılacaktır timeout. Varsayılanı ayarlamak Noneve işlevin ilk satırında ekleme yapmak çok daha iyidir kwargs = kwargs or {}. Args iyi çünkü tuples değişmez değil.
scottmrogowski

32

Birim testlerinde bir zaman aşımı çağrısı ararken bu iş parçacığında koştum. Yanıtları veya 3. parti paketlerinde basit bir şey bulamadım, bu yüzden aşağıdaki dekoratörü yazdınız, hemen koda bırakabilirsiniz:

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

Sonra bir testi veya istediğiniz herhangi bir işlevi zaman aşımı yapmak için bu kadar basit:

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

14
Zaman aşımına ulaşıldıktan sonra bu işlev sona ermediğinden dikkatli olun!
Sylvain

Windows'ta, bu tamamen yeni bir süreç ortaya çıkarır - ki bu zaman aşımı zamanına, belki de bağımlılıkların kurulması uzun zaman alıyorsa çok fazla zaman alacaktır.
Aaron Hall

1
Evet, bunun biraz düzeltilmesi gerekiyor. İplikleri sonsuza dek devam ettirir.
sudo

2
IDK bu en iyi Exceptionyolsa , ancak func_wrapper'ın içinde deneyebilir / yakalayabilir pool.close()ve yakalamadan sonra ne olursa olsun ipliğin her zaman sonra ölmesini sağlamak için yapabilirsiniz. Sonra TimeoutErrorya da sonra ne istersen atabilirsin . Benim için iş gibi görünüyor.
sudo

2
Bu yararlı, ama bir kez yaptım bir kez, anladım RuntimeError: can't start new thread. Eğer görmezden gelirsem işe yarayacak mı yoksa bu sorunu aşmak için yapabileceğim başka bir şey var mı? Şimdiden teşekkürler!
Benjie

20

stopitPypi bulunan paket, iyi zaman aşımına işlemek gibi görünüyor.

Ben dekore ne beklediğiniz ne yapar, @stopit.threading_timeoutablebir timeoutparametre ekler dekoratör gibi, işlevi durdurur.

Pypi'ye göz atın: https://pypi.python.org/pypi/stopit


1
Çok kullanışlı ve iplik açısından güvenlidir! Teşekkürler ve Artı bir! Bu şimdiye kadar bulduğum en iyi seçenek ve kabul edilen cevaptan daha iyi !!
Yahya

Kütüphane iddiaları, bazı işlevler Windows'da çalışmaz.
Stefan Simik

16

Çok fazla öneri var, ama hiçbiri concurrent.futures kullanmıyor, bence bu işin en okunaklı yolu.

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)

Okuması ve bakımı süper basit.

Bir havuz oluşturuyoruz, tek bir işlem gönderiyoruz ve sonra ihtiyacınız olan her şeyi yakalayabileceğiniz ve işleyebileceğiniz bir TimeoutError'u yükseltmeden önce 5 saniye bekleyin.

Python 3.2+'ye özgüdür ve 2.7'ye geri bildirilir (pip install futures).

İş parçacıkları ve işlemler arasında geçiş yapmak ProcessPoolExecutor,ThreadPoolExecutor .

Süreci zaman aşımına uğratmak istiyorsanız Çakıl taşına bakmanızı öneririm .


2
"Uyarı: zaman aşımı durumunda işlev sonlandırılmaz" ne anlama geliyor?
Scott Stafford

5
@ScottStafford Süreçler / iş parçacıkları bir TimeoutError hatası yükseltildiği için bitmez. Bu nedenle, işlem veya iş parçacığı yine de tamamlanmaya çalışacak ve zaman aşımı sırasında otomatik olarak size geri denetim vermeyecektir.
Brian

Bu, o sırada ara sonuçları kaydetmeme izin verir mi? Örneğin, zaman aşımı süresini 5 olarak ayarladığım özyinelemeli işlevim varsa ve bu süre içinde kısmi sonuçlara sahipsem, zaman aşımı sırasında kısmi sonuçları döndürmek için işlevi nasıl yazabilirim?
SumNeuron

Tam olarak bunu kullanıyorum, ancak 1000 görevim var, her birine zaman aşımından 5 saniye önce izin veriliyor. Benim sorunum, zaman aşımı sadece bireysel görevlerde değil toplam görevlere uygulandığı için asla bitmeyen görevlerde çekirdeklerin tıkanmasıdır. concurrent.futures bu afaik için bir çözüm sağlamaz.
Bastiaan

12

Harika, kullanımı kolay ve güvenilir PyPi projesi zaman aşımı-dekoratör ( https://pypi.org/project/timeout-decorator/ )

kurulum :

pip install timeout-decorator

Kullanımı :

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
Açık çözümü takdir ediyorum. Ancak herkes bu kütüphanenin nasıl çalıştığını açıklayabilir mi, özellikle de çoklu iş parçacığıyla uğraşırken. Şahsen ben konuları veya sinyalleri işlemek için bilinmeyen bir makhanizm kullanmaktan korkuyorum.
wsysuper

@wsysuper the lib'in 2 çalışma modu vardır: yeni iş parçacığı veya yeni bir alt işlem (iş parçacığı için güvenli olduğu varsayılır) açma
Gil

bu benim için çok iyi çalıştı!
Florian Heigl

6

Wrapt_timeout_decorator yazarıyım

Burada sunulan çözümlerin çoğu ilk bakışta Linux altında harika çalışıyor - çünkü fork () ve sinyaller () var - ama pencerelerde işler biraz farklı görünüyor. Ve Linux'ta altyazılar söz konusu olduğunda, artık Sinyalleri kullanamazsınız.

Bir işlemi Windows altında oluşturmak için seçilebilir olması gerekir - ve birçok süslü işlev veya Sınıf yöntemi değildir.

Bu yüzden dereotu ve çok işlemli (turşu ve çoklu işleme değil) gibi daha iyi bir toplayıcı kullanmanız gerekir - bu yüzden ProcessPoolExecutor'u (veya sadece sınırlı işlevsellikle) kullanamazsınız.

Zaman aşımının kendisi için - Zaman aşımının ne anlama geldiğini tanımlamanız gerekir - çünkü Windows'da süreci ortaya çıkarmak için önemli (ve belirlenemez değil) zaman alacaktır. Kısa zaman aşımlarında bu zor olabilir. Varsayalım, sürecin ortaya çıkması yaklaşık 0,5 saniye sürüyor (kolayca !!!). Eğer 0.2 saniyelik bir zaman aşımı verirseniz ne olur? İşlev 0,5 + 0,2 saniye sonra zaman aşımına uğramalı mı (yöntemi 0,2 saniye çalıştırsın)? Ya da çağrılan işlem 0.2 saniye sonra zaman aşımına uğrar (bu durumda, dekore edilmiş fonksiyon HER ZAMAN zaman aşımına uğrar, çünkü o zaman yumurtlanmaz)

Ayrıca iç içe dekoratörler kötü olabilir ve bir alt dizide Sinyalleri kullanamazsınız. Gerçekten evrensel, platformlar arası bir dekoratör oluşturmak istiyorsanız, tüm bunların dikkate alınması (ve test edilmesi) gerekir.

Diğer sorunlar, istisnaları arayana geri aktarmanın yanı sıra günlüğe kaydetme sorunlarıdır (süslü işlevde kullanılıyorsa - başka bir işlemdeki dosyalara günlük kaydı desteklenmez)

Tüm kenar vakaları kapsamayı denedim, wrapt_timeout_decorator paketine bakabilir veya en azından orada kullanılan birim testlerden esinlenerek kendi çözümlerinizi test edebilirsiniz.

@Alexis Eggermont - ne yazık ki yorum yapmak için yeterli puanım yok - belki başka biri sizi bilgilendirebilir - Sanırım ithalat sorununuzu çözdüm.


3

timeout-decorator windows sisteminde çalışmaz, windows desteklemiyordu signal iyi .

Windows sisteminde timeout-decorator kullanırsanız aşağıdakileri alırsınız

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

Bazılarının kullanılması önerildi use_signals=False ama benim için çalışmadı.

Author @bitranox aşağıdaki paketi yarattı:

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

Kod Örneği:

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şağıdaki istisnayı verir:

TimeoutError: Function mytest timed out after 5 seconds

Bu ses çok hoş bir çözüm gibi. Tuhaf bir şekilde, hat from wrapt_timeout_decorator import * diğer ithalatlarımın bazılarını öldürüyor gibi görünüyor. Örneğin anladım ModuleNotFoundError: No module named 'google.appengine', ancak wrapt_timeout_decorator'ı içe aktarmazsam bu hatayı almıyorum
Alexis Eggermont

@AlexisEggermont Bunu appengine ile kullanmak üzereydim ... bu yüzden bu hata devam ederse çok merak ediyorum?
PascalVKooten

2

Aynı sinyalleri kullanabiliriz. Aşağıdaki örneğin sizin için yararlı olacağını düşünüyorum. İpliklere kıyasla çok basit.

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
Belirli bir istisna seçmek ve sadece onu yakalamak daha iyi olur. Çıplak try: ... except: ...her zaman kötü bir fikirdir.
hivert

Sana katılıyorum.
AR

nedenini anlıyorum, bir sysadmin / entegratör olarak katılmıyorum - python kodu hata işleme ihmal için kötü şöhretli ve beklediğiniz bir şey işlemek kaliteli yazılım için yeterli değil. planladığınız 5 şeyi VE diğer şeyler için genel bir stratejiyi ele alabilirsiniz. "Geri izleme, Hiçbiri" bir strateji değil, bir hakarettir.
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
Bu kod soruyu cevaplayabilirken, sorunun nasıl ve / veya neden
çözüldüğüne

1

Ben ihtiyaç vardı nestable (iplik dayalı yaklaşım yapamaz olan) time.sleep tarafından bloke almazsınız zamanlanmış kesmeleri (SIGALARM yapamaz olan). Buradan kodu kopyalayıp hafifçe değiştirdim: http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/

Kodun kendisi:

#!/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()

ve bir kullanım örneği:

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.'

Bu ayrıca sinyal kullanır, bu nedenle bir iş parçacığından çağrılırsa çalışmaz.
garg10may

0

İşte verilen iplik bazlı çözümde küçük bir gelişme.

Aşağıdaki kod istisnaları destekler :

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]

5 saniyelik bir zaman aşımı ile çağırmak:

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

1
Bu, orijinal geri izlemeyi gizleyen yeni bir istisna oluşturacaktır. Aşağıdaki
sürümüme

1
Bu aynı zamanda güvenli değildir, sanki runFunctionCatchExceptions()GIL elde etmek için belirli Python fonksiyonları içinde çağrılır. Örneğin şu would never veya çok uzun süre, işlev içinde denir eğer dönüş: eval(2**9999999999**9999999999). Bkz. Stackoverflow.com/questions/22138190/…
Mikko Ohtamaa
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.