python异步线程异常处理

时间:2013-11-27 05:15:36

标签: python multithreading exception asynchronous ctypes

我正在尝试在Python中实现超时功能。

它通过使用函数装饰器包装函数来工作,该函数装饰器将函数作为线程调用,但也调用“看门狗”线程,该线程将在指定的时间段过后在函数线程中引发异常。

它目前适用于不睡眠的线程。在do_rand调用期间,我怀疑在time.sleep调用之后以及在执行超出try/except块之后实际调用了'异步'异常,因为这可以解释{Unhandled exception in thread started by 1}}错误。此外,do_rand来电时的错误是在通话后7秒(time.sleep的持续时间)生成的。

我如何进行'唤醒'线程(使用ctypes?)以使其响应异步异常?

或者可能是一种不同的方法?

代码:

# Import System libraries
import ctypes
import random
import sys
import threading
import time

class TimeoutException(Exception):
    pass

def terminate_thread(thread, exc_type = SystemExit):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(exc_type)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)

    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class timeout_thread(threading.Thread):
    def __init__(self, interval, target_thread):
        super(timeout_thread, self).__init__()
        self.interval     = interval
        self.target_thread = target_thread
        self.done_event = threading.Event()
        self.done_event.clear()

    def run(self):
        timeout = not self.done_event.wait(self.interval)
        if timeout:
            terminate_thread(self.target_thread, TimeoutException)

class timeout_wrapper(object):
    def __init__(self, interval = 300):
        self.interval = interval

    def __call__(self, f):
        def wrap_func(*args, **kwargs):
            thread = threading.Thread(target = f, args = args, kwargs = kwargs)
            thread.setDaemon(True)
            timeout_ticker = timeout_thread(self.interval, thread)
            timeout_ticker.setDaemon(True)
            timeout_ticker.start()
            thread.start()
            thread.join()
            timeout_ticker.done_event.set()
        return wrap_func

@timeout_wrapper(2)
def print_guvnah():
    try:
        while True:
            print "guvnah"

    except TimeoutException:
        print "blimey"

def print_hello():
    try:
        while True:
            print "hello"

    except TimeoutException:
        print "Whoops, looks like I timed out"

def do_rand(*args):
    try:
        rand_num   = 7 #random.randint(0, 10)
        rand_pause = 7 #random.randint(0,  5)
        print "Got rand: %d" % rand_num
        print "Waiting for %d seconds" % rand_pause
        time.sleep(rand_pause)
    except TimeoutException:
        print "Waited too long"

print_guvnah()
timeout_wrapper(3)(print_hello)()
timeout_wrapper(2)(do_rand)()

2 个答案:

答案 0 :(得分:2)

问题是time.sleep阻止。它实际上很难阻塞,因此唯一可以实际中断它的是过程信号。但是带有它的代码变得非常混乱,在某些情况下甚至信号都不起作用(例如,当你正在阻止socket.recv()时,请看:recv() is not interrupted by a signal in multithreaded environment)。

因此,通常无法中断线程(不会终止整个进程)(更不用说有人可以简单地覆盖线程中的信号处理)。

但在这种特殊情况下,您可以使用来自线程模块的time.sleep类,而不是使用Event

主题1

from threading import Event

ev = Event()
ev.clear()

state = ev.wait(rand_pause) # this blocks until timeout or .set() call

主题2 (确保它可以访问相同的ev实例)

ev.set() # this will unlock .wait above

请注意state将是事件的内部状态。因此,state == True表示已使用.set()解锁,state == False表示发生超时。

在此处阅读有关活动的更多信息:

http://docs.python.org/2/library/threading.html#event-objects

答案 1 :(得分:1)

你需要使用睡眠以外的东西,或者你需要向另一个线程发送信号才能唤醒它。

我使用的一个选项是设置一对文件描述符并使用select或poll而不是sleep,这允许你向文件描述符写一些内容来唤醒另一个线程。或者你只是等待,直到睡眠结束,如果你需要的是操作错误,因为它花了太长时间没有别的依赖它。