หมดเวลาในการเรียกใช้ฟังก์ชัน


300

ฉันกำลังเรียกใช้ฟังก์ชันใน Python ซึ่งฉันรู้ว่าอาจหยุดและบังคับให้ฉันรีสตาร์ทสคริปต์

ฉันจะเรียกฟังก์ชั่นได้อย่างไรหรือฉันจะปิดมันเพื่อที่ว่าถ้ามันใช้เวลานานกว่า 5 วินาทีสคริปต์จะยกเลิกมันและทำอย่างอื่น?

คำตอบ:


227

คุณสามารถใช้แพ็คเกจสัญญาณหากคุณใช้งานบน 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 วินาทีหลังจากการโทรalarm.alarm(10)จะเรียกตัวจัดการ สิ่งนี้ทำให้เกิดข้อยกเว้นที่คุณสามารถดักจับได้จากรหัส Python ปกติ

โมดูลนี้เล่นได้ไม่ดีกับกระทู้

โปรดทราบว่าเนื่องจากเราเพิ่มข้อยกเว้นเมื่อการหมดเวลาเกิดขึ้นอาจสิ้นสุดการตรวจจับและละเว้นภายในฟังก์ชันเช่นหนึ่งฟังก์ชันดังกล่าว:

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

5
ฉันใช้ Python 2.5.4 มีข้อผิดพลาดดังกล่าว: Traceback (การโทรล่าสุดครั้งล่าสุด): ไฟล์ "aa.py", บรรทัดที่ 85, ใน func signal.signal (signal.SIGALRM, ตัวจัดการ) AttributeError: 'โมดูล' วัตถุไม่มีแอตทริบิวต์ 'SIGALRM'
flypen

11
@flypen นั่นเป็นเพราะsignal.alarmและสิ่งที่เกี่ยวข้องSIGALRMไม่สามารถใช้ได้บนแพลตฟอร์ม Windows
AA สองเท่า

2
หากมีกระบวนการจำนวนมากและการโทรแต่ละครั้งsignal.signalจะทำงานอย่างถูกต้องหรือไม่ การsignal.signalโทรแต่ละครั้งจะไม่ยกเลิก "พร้อมกัน" หรือไม่
Brownian

1
คำเตือนสำหรับผู้ที่ต้องการใช้สิ่งนี้พร้อมกับส่วนขยาย C: ตัวจัดการสัญญาณ Python จะไม่ถูกเรียกใช้จนกว่าฟังก์ชัน C จะส่งคืนการควบคุมไปยังล่าม Python สำหรับกรณีการใช้งานนี้ใช้คำตอบของ ATOzTOA: stackoverflow.com/a/14924210/1286628
wkschwartz

13
ฉันสองคำเตือนเกี่ยวกับหัวข้อ สัญญาณสัญญาณเตือนทำงานบนเธรดหลักเท่านั้น ฉันพยายามที่จะใช้สิ่งนี้ในมุมมอง Django - ล้มเหลวทันทีด้วยคำฟุ่มเฟือยเกี่ยวกับหัวข้อหลักเท่านั้น
JL Peyret

154

คุณสามารถใช้multiprocessing.Processเพื่อทำสิ่งนั้น

รหัส

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
ฉันจะรับค่าตอบแทนของวิธีการเป้าหมายได้อย่างไร
bad_keypoints

4
ดูเหมือนจะไม่ทำงานหากฟังก์ชั่นที่เรียกใช้ติดอยู่บนบล็อก I / O
sudo

4
@bad_keypoints ดูคำตอบนี้: stackoverflow.com/a/10415215/1384471โดยทั่วไปคุณจะผ่านรายการที่คุณใส่คำตอบไว้
ปีเตอร์

1
@sudo join()แล้วเอา ที่ทำให้ x join(10)จำนวนของคุณของกระบวนการย่อยพร้อมกันเป็นที่ทำงานจนกว่าพวกเขาเสร็จสิ้นการทำงานของพวกเขาหรือจำนวนเงินที่กำหนดไว้ใน ในกรณีที่คุณมีการบล็อก I / O เป็นเวลา 10 กระบวนการโดยใช้การเข้าร่วม (10) คุณได้ตั้งค่าให้รอทั้งหมด 10 กระบวนการสำหรับกระบวนการ EACH ที่เริ่มต้นแล้ว ใช้การตั้งค่าสถานะ daemon เช่นstackoverflow.com/a/27420072/2480481ตัวอย่างนี้ แน่นอนคุณสามารถส่งแฟล็กdaemon=Trueไปยังmultiprocessing.Process()ฟังก์ชันได้โดยตรง
m3nda

2
@ATOzTOA ปัญหาเกี่ยวกับการแก้ปัญหานี้อย่างน้อยก็เพื่อจุดประสงค์ของฉันคือมันอาจไม่อนุญาตให้เด็ก ๆ เดินไปทำความสะอาดหลังจากพวกเขา จากเอกสารของฟังก์ชั่นยุติ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.
abalcerek

78

ฉันจะเรียกฟังก์ชั่นได้อย่างไรหรือฉันจะปิดมันเพื่อที่ว่าถ้ามันใช้เวลานานกว่า 5 วินาทีสคริปต์จะยกเลิกมัน?

ผมโพสต์กระทู้ที่แก้คำถามนี้ / threading.Timerมีปัญหากับมัณฑนากรและ นี่คือการสลาย

นำเข้าและตั้งค่าเพื่อความเข้ากันได้

ผ่านการทดสอบกับ Python 2 และ 3 และควรทำงานภายใต้ Unix / Linux และ Windows

ก่อนนำเข้า ความพยายามในการรักษารหัสให้สอดคล้องกันโดยไม่คำนึงถึงรุ่น Python:

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

ใช้รหัสอิสระเวอร์ชัน:

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

ตอนนี้เราได้นำเข้าฟังก์ชันการทำงานของเราจากไลบรารีมาตรฐาน

exit_after มัณฑนากร

ต่อไปเราต้องการฟังก์ชั่นเพื่อยกเลิกการmain()จากเธรดลูก:

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

และนี่คือมัณฑนากร:

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

การใช้

และนี่คือการใช้งานที่ตอบคำถามของคุณโดยตรงเกี่ยวกับการออกหลังจาก 5 วินาที!:

@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')

การสาธิต:

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

การเรียกใช้ฟังก์ชันที่สองจะไม่เสร็จสิ้น แต่กระบวนการควรออกด้วยการย้อนกลับ!

KeyboardInterrupt ไม่หยุดด้ายนอนเสมอ

โปรดทราบว่าสลีปจะไม่ถูกขัดจังหวะโดยคีย์บอร์ดขัดจังหวะบน Python 2 บน Windows เช่น:

@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

และไม่น่าจะมีการอินเตอร์รัปต์โค้ดที่รันในส่วนขยายเว้นแต่จะตรวจสอบอย่างชัดเจนPyErr_CheckSignals()ดู Cython, Python และ KeyboardInterrupt ละเว้น

ฉันจะหลีกเลี่ยงการนอนด้ายมากกว่าวินาทีในกรณีใด ๆ - นั่นคือกัปในเวลาประมวลผล

ฉันจะเรียกฟังก์ชั่นได้อย่างไรหรือฉันจะปิดมันเพื่อที่ว่าถ้ามันใช้เวลานานกว่า 5 วินาทีสคริปต์จะยกเลิกมันและทำอย่างอื่น?

เพื่อจับมันและทำอย่างอื่นคุณสามารถจับ KeyboardInterrupt

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

ฉันยังไม่ได้อ่านโพสต์ทั้งหมดของคุณ แต่ฉันแค่สงสัยว่าจะเกิดอะไรขึ้นถ้าฟลัชเป็น 0 ที่จะตีความว่าเป็นเท็จในคำสั่ง if ภายใต้ใช่มั้ย
Koenraad van Duin

2
ทำไมฉันต้องโทรthread.interrupt_main()ทำไมฉันจึงยกข้อยกเว้นไม่ได้โดยตรง
Anirban Nag 'tintinmj'

มีความคิดเห็นเกี่ยวกับการห่อหุ้มmultiprocessing.connection.Clientด้วยไหม? - พยายามที่จะแก้ปัญหา: stackoverflow.com/questions/57817955/…
wwii

51

ฉันมีข้อเสนอที่แตกต่างกันซึ่งเป็นฟังก์ชั่นที่บริสุทธิ์ (มี API เดียวกับข้อเสนอแนะการเธรด) และดูเหมือนว่าจะทำงานได้ดี (ตามคำแนะนำในหัวข้อนี้)

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
คุณควรคืนค่าตัวจัดการสัญญาณดั้งเดิม ดูstackoverflow.com/questions/492519/…
Martin Konecny

9
อีกหนึ่งหมายเหตุ: วิธีสัญญาณ Unix ใช้งานได้เฉพาะในกรณีที่คุณใช้มันในเธรดหลัก การนำไปใช้ในเธรดย่อยจะแสดงข้อยกเว้นและจะไม่ทำงาน
Martin Konecny

12
นี่ไม่ใช่ทางออกที่ดีที่สุดเพราะใช้งานได้กับ linux เท่านั้น
สูงสุด

17
สูงสุดไม่เป็นความจริง - ทำงานบนยูนิกซ์ที่สอดคล้องกับ POSIX ใด ๆ ฉันคิดว่าความคิดเห็นของคุณควรแม่นยำกว่านี้ไม่ทำงานบน Windows
Chris Johnson

6
คุณควรหลีกเลี่ยงการตั้งค่า kwargs เป็น dict ที่ว่างเปล่า Python gotcha ทั่วไปก็คืออาร์กิวเมนต์เริ่มต้นในฟังก์ชั่นจะไม่แน่นอน timeoutดังนั้นพจนานุกรมที่จะใช้ร่วมกันในทุกสายไป มันจะดีกว่ามากที่จะตั้งค่าเริ่มต้นและในบรรทัดแรกของฟังก์ชั่นเพิ่มNone kwargs = kwargs or {}Args ไม่เป็นไรเพราะสิ่งอันดับไม่สามารถเปลี่ยนแปลงได้
scottmrogowski

32

ฉันวิ่งข้ามกระทู้นี้เมื่อค้นหาหมดเวลาในการทดสอบหน่วย ฉันไม่พบอะไรง่ายๆในคำตอบหรือแพ็คเกจของบุคคลที่สามดังนั้นฉันจึงเขียนมัณฑนากรด้านล่างคุณสามารถวางลงในรหัสได้

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

จากนั้นมันง่ายพอ ๆ กับการหมดเวลาทดสอบหรือฟังก์ชั่นใด ๆ ที่คุณต้องการ:

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

14
ระวังตั้งแต่นี้จะไม่ยุติฟังก์ชั่นหลังจากหมดเวลา!
Sylvain

โปรดทราบว่าใน Windows สิ่งนี้จะทำให้เกิดกระบวนการใหม่ทั้งหมด - ซึ่งจะกินเข้าไปในช่วงเวลาที่หมดเวลาบางทีอาจจะมากถ้าการพึ่งพาต้องใช้เวลานานในการตั้งค่า
Aaron Hall

1
ใช่มันต้องมีการปรับแต่ง มันทำให้กระทู้ไปตลอดกาล
sudo

2
IDK ถ้านี่เป็นวิธีที่ดีที่สุด แต่คุณสามารถลอง / Exceptionเข้าไปด้านในของ func_wrapper และทำpool.close()ตามจับเพื่อให้แน่ใจว่าเธรดจะตายเสมอหลังจากนั้นไม่ว่าอะไรจะเกิดขึ้น จากนั้นคุณสามารถโยนTimeoutErrorหรือสิ่งที่คุณต้องการหลังจาก ดูเหมือนว่าจะทำงานให้ฉัน
sudo

2
นี้จะเป็นประโยชน์ RuntimeError: can't start new threadแต่เมื่อฉันได้ทำมันจำนวนมากครั้งที่ฉันได้รับ มันจะยังใช้งานได้ถ้าฉันไม่สนใจมันหรือมีอย่างอื่นที่ฉันสามารถทำได้เพื่อแก้ไขปัญหานี้หรือไม่? ขอบคุณล่วงหน้า!
Benjie

20

stopitแพคเกจที่พบใน pypi, ดูเหมือนว่าจะจัดการหมดเวลาดี

ฉันชอบ@stopit.threading_timeoutableมัณฑนากรซึ่งเพิ่มtimeoutพารามิเตอร์ให้กับฟังก์ชั่นการตกแต่งซึ่งทำในสิ่งที่คุณคาดหวังมันหยุดฟังก์ชั่น

ลองใช้งาน pypi: https://pypi.python.org/pypi/stopit


1
มันมีประโยชน์มากและปลอดภัยสำหรับเธรด! ขอขอบคุณและบวกหนึ่ง! นี่เป็นตัวเลือกที่ดีที่สุดที่ฉันเคยพบและดีกว่าคำตอบที่ยอมรับ !!
ยะห์ยา

ห้องสมุดอ้างสิทธิ์การใช้งานบางอย่างไม่สามารถใช้งานได้ใน Windows
Stefan Simik

16

มีคำแนะนำมากมาย แต่ไม่มีใครใช้งานพร้อมกันได้ในอนาคตซึ่งฉันคิดว่าเป็นวิธีที่ชัดเจนที่สุดในการจัดการสิ่งนี้

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)

ง่ายสุดในการอ่านและบำรุงรักษา

เราสร้างกลุ่มส่งกระบวนการเดียวจากนั้นรอนานถึง 5 วินาทีก่อนเพิ่ม TimeoutError ที่คุณสามารถจับและจัดการได้ตามที่คุณต้องการ

เนทิฟไปเป็นไพ ธ อน 3.2+ และย้อนกลับสู่ 2.7 (ฟิวเจอร์ส pip ติดตั้ง)

สลับไปมาระหว่างหัวข้อและกระบวนการเป็นง่ายๆเป็นแทนที่ด้วยProcessPoolExecutorThreadPoolExecutor

หากคุณต้องการที่จะยุติกระบวนการในการหมดเวลาฉันขอแนะนำให้มองเข้าไปในPebble


2
"คำเตือน: สิ่งนี้จะไม่ยุติฟังก์ชั่นหากการหมดเวลา" หมายถึงอะไร
Scott Stafford

5
กระบวนการ @ScottStafford / กระทู้ไม่ได้จบเพียงเพราะ TimeoutError ได้รับการยก ดังนั้นกระบวนการหรือเธรดจะยังคงพยายามเรียกใช้ให้เสร็จและจะไม่ให้การควบคุมกลับโดยอัตโนมัติเมื่อหมดเวลา
Brian

นี่ช่วยให้ฉันบันทึกผลลัพธ์ที่อยู่ตรงกลางในขณะนั้นได้หรือไม่? เช่นถ้าฉันมีฟังก์ชันแบบเรียกซ้ำที่ฉันตั้งค่าการหมดเวลาเป็น 5 และในเวลานั้นฉันมีผลลัพธ์บางส่วนฉันจะเขียนฟังก์ชันเพื่อคืนผลลัพธ์บางส่วนเมื่อหมดเวลาได้อย่างไร
SumNeuron

ฉันใช้สิ่งนี้อย่างแน่นอน แต่ฉันมีงาน 1,000 งานแต่ละงานได้รับอนุญาต 5 วินาทีก่อนหมดเวลา ปัญหาของฉันคือการที่แกนประมวลผลอุดตันกับงานที่ไม่เคยสิ้นสุดเพราะการหมดเวลาใช้งานกับจำนวนงานที่ไม่ได้อยู่ในงานแต่ละงานเท่านั้น concurrent.futures ไม่ได้ให้คำตอบสำหรับ afaik นี้
Bastiaan

12

ยอดเยี่ยมใช้งานง่ายและเชื่อถือได้ของPyPi project timeout-decorator ( https://pypi.org/project/timeout-decorator/ )

การติดตั้ง :

pip install timeout-decorator

การใช้งาน :

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
ฉันขอขอบคุณทางออกที่ชัดเจน แต่ทุกคนสามารถอธิบายได้ว่าห้องสมุดนี้ทำงานอย่างไรโดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับมัลติเธรด โดยส่วนตัวฉันกลัวที่จะใช้กลไกที่ไม่รู้จักเพื่อจัดการกระทู้หรือสัญญาณ
wsysuper

@wsysuper lib มี 2 โหมดของการดำเนินงาน: เปิดหัวข้อใหม่หรือกระบวนการย่อยใหม่ (ซึ่งคิดว่าจะปลอดภัยด้าย)
กิล

มันทำงานได้ดีมากสำหรับฉัน!
Florian Heigl

6

ฉันเป็นผู้เขียน wrapt_timeout_decorator

โซลูชันส่วนใหญ่ที่นำเสนอในที่นี้ทำงานภายใต้ Linux อย่างรวดเร็วในการมองเห็นครั้งแรกเนื่องจากเรามี fork () และ signal () - แต่ใน windows สิ่งต่าง ๆ ดูแตกต่างออกไปเล็กน้อย และเมื่อพูดถึงหัวข้อย่อยบน Linux คุณไม่สามารถใช้สัญญาณได้อีกต่อไป

เพื่อที่จะวางไข่กระบวนการใน Windows มันจะต้องสามารถเลือกได้ - และฟังก์ชั่นการตกแต่งจำนวนมากหรือวิธีการเรียนไม่ได้

ดังนั้นคุณจำเป็นต้องใช้ตัวเลือกที่ดีกว่าเช่น dill และ multiprocess (ไม่ใช่ pickle และ multiprocessing) - นั่นเป็นสาเหตุที่คุณไม่สามารถใช้ ProcessPoolExecutor (หรือมีฟังก์ชันที่ จำกัด เท่านั้น)

สำหรับการหมดเวลาเอง - คุณต้องกำหนดความหมายของการหมดเวลา - เนื่องจากใน Windows จะใช้เวลา (และไม่สามารถกำหนดได้) ในการวางกระบวนการ นี่อาจเป็นเรื่องยุ่งยากในช่วงเวลาสั้น ๆ สมมติว่าการวางไข่กระบวนการใช้เวลาประมาณ 0.5 วินาที (ง่าย !!!) หากคุณให้เวลากับ 0.2 วินาทีจะเกิดอะไรขึ้น ฟังก์ชันควรหมดเวลาหลังจาก 0.5 + 0.2 วินาที (เพื่อให้วิธีการทำงานเป็นเวลา 0.2 วินาที)? หรือกระบวนการที่เรียกว่าการหมดเวลาหลังจาก 0.2 วินาที (ในกรณีนั้นฟังก์ชั่นการตกแต่งจะหมดเวลาเสมอเพราะในเวลานั้นมันไม่ได้เกิดใหม่)?

นักตกแต่งที่ซ้อนกันอาจเป็นสิ่งที่น่ารังเกียจและคุณไม่สามารถใช้สัญญาณในชุดข้อความย่อยได้ หากคุณต้องการสร้างมัณฑนากรข้ามแพลตฟอร์มที่เป็นสากลอย่างแท้จริงทั้งหมดนี้ต้องนำมาพิจารณาด้วย (และทดสอบ)

ปัญหาอื่น ๆ กำลังส่งข้อยกเว้นกลับไปยังผู้โทรรวมถึงปัญหาการบันทึก (หากใช้ในฟังก์ชั่นการตกแต่ง - ไม่รองรับการบันทึกไฟล์ในกระบวนการอื่น)

ฉันพยายามที่จะครอบคลุมทุกกรณีขอบคุณอาจมองเข้าไปในแพ็คเกจ wrapt_timeout_decorator หรืออย่างน้อยทดสอบโซลูชันของคุณเองที่ได้แรงบันดาลใจจาก unittests ที่ใช้

@Alexis Eggermont - น่าเสียดายที่ฉันไม่มีคะแนนพอที่จะแสดงความคิดเห็น - บางทีคนอื่นสามารถแจ้งให้คุณทราบ - ฉันคิดว่าฉันได้แก้ไขปัญหาการนำเข้าของคุณแล้ว


3

timeout-decoratorไม่ทำงานบนระบบ windows เนื่องจาก windows ไม่รองรับsignalอย่างดี

หากคุณใช้ timeout-decorator ในระบบ windows คุณจะได้รับสิ่งต่อไปนี้

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

บางคนแนะนำให้ใช้use_signals=Falseแต่ไม่ได้ผลสำหรับฉัน

Author @bitranox สร้างแพ็คเกจต่อไปนี้:

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

ตัวอย่างโค้ด:

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()

ให้ข้อยกเว้นต่อไปนี้:

TimeoutError: Function mytest timed out after 5 seconds

เสียงนี้เป็นทางออกที่ดีมาก น่าประหลาดที่เส้นfrom wrapt_timeout_decorator import * ดูเหมือนจะฆ่าการนำเข้าอื่น ๆ ของฉัน ตัวอย่างเช่นฉันได้รับModuleNotFoundError: No module named 'google.appengine'แต่ฉันไม่ได้รับข้อผิดพลาดนี้หากฉันไม่นำเข้า wrapt_timeout_decorator
Alexis Eggermont

@AlexisEggermont ฉันกำลังจะใช้สิ่งนี้กับ appengine ... ดังนั้นฉันสงสัยมากว่าข้อผิดพลาดนี้ยังคงอยู่หรือไม่
PascalVKooten

2

เราสามารถใช้สัญญาณสำหรับสิ่งเดียวกัน ฉันคิดว่าตัวอย่างด้านล่างจะมีประโยชน์สำหรับคุณ มันง่ายมากเมื่อเทียบกับหัวข้อ

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
มันจะเป็นการดีกว่าถ้าคุณเลือกข้อยกเว้นเฉพาะและจับเท่านั้น ความเปลือยtry: ... except: ...อยู่เสมอความคิดที่ไม่ดี
hivert

ฉันเห็นด้วยกับคุณ hivert
AR

ในขณะที่ฉันเข้าใจเหตุผลในฐานะผู้ดูแลระบบ / ผู้รวบรวมฉันไม่เห็นด้วย - รหัสหลามนั้นมีชื่อเสียงในเรื่องการละเลยการจัดการข้อผิดพลาดและการจัดการสิ่งเดียวที่คุณคาดหวังนั้นไม่ดีพอสำหรับซอฟต์แวร์ที่มีคุณภาพ คุณสามารถจัดการกับ 5 สิ่งที่คุณวางแผนและเป็นกลยุทธ์ทั่วไปสำหรับสิ่งอื่น ๆ "Traceback, None" ไม่ใช่กลยุทธ์มันเป็นการดูถูก
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
ในขณะที่รหัสนี้อาจตอบคำถามให้บริบทเพิ่มเติมเกี่ยวกับวิธีการและ / หรือทำไมมันแก้ปัญหาจะปรับปรุงมูลค่าระยะยาวของคำตอบ
Dan Cornilescu

1

ผมมีความจำเป็นในการNestableขัดจังหวะเวลาที่กำหนด (ซึ่ง SIGALARM ไม่สามารถทำ) ที่จะได้รับการบล็อกโดย time.sleep (ซึ่งวิธีด้ายไม่สามารถทำ) ฉันสิ้นสุดการคัดลอกและแก้ไขรหัสจากที่นี่: http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/

รหัสตัวเอง:

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

และตัวอย่างการใช้งาน:

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

สิ่งนี้ยังใช้สัญญาณดังนั้นจะไม่ทำงานหากเรียกจากเธรด
garg10may

0

นี่คือการปรับปรุงเล็กน้อยสำหรับโซลูชันแบบอิงเธรดที่กำหนด

รหัสด้านล่างรองรับข้อยกเว้น :

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 วินาที:

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

1
สิ่งนี้จะทำให้เกิดข้อยกเว้นใหม่ซึ่งซ่อนการติดตามกลับดั้งเดิม ดูรุ่นของฉันด้านล่าง ...
Meitham

1
สิ่งนี้ยังไม่ปลอดภัยราวกับว่าในrunFunctionCatchExceptions()บางฟังก์ชั่นของ Python ที่รับ GIL ถูกเรียกใช้ eval(2**9999999999**9999999999)เช่นต่อไปนี้จะไม่เคยหรือเป็นเวลานานมากกลับมาถ้าเรียกว่าภายในฟังก์ชั่น: ดูstackoverflow.com/questions/22138190/…
Mikko Ohtamaa
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.