python 主线程捕获子线程异常

问题原因:

一个线程拥有自己独立的上下文和调用栈,如果该线程(调用栈)中 抛出异常之后,调用方是无法捕获的。

解决方法1:

sys.exc_info()可以返回当前的异常,作为一个属性保存下来,然后在线程的join方法中重新抛出异常。

https://stackoverflow.com/questions/2829329/catch-a-threads-exception-in-the-caller-thread-in-python/12223550#12223550

#! python3
#-*- coding:utf-8 --
'''
Created on 2019年7月7日

@author: Administrator
'''
import threading
from _ast import Raise
from time import sleep

class ExcThread(threading.Thread):
    def __init__(self,group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        if kwargs is None:
            kwargs = {}
        self.__target = target
        self.__args = args
        self.__kwargs = kwargs
        
    def run(self):
        self.exc = None
        try:
            # Possibly throws an exception
            if self.__target:
                self.__target(*self.__args, **self.__kwargs)
        except Exception as e:
            import sys
            self.exc = sys.exc_info()
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self.__target, self.__args, self.__kwargs
    # Save details of the exception thrown but don't rethrow,
    # just complete the function
    
    def join(self):
        threading.Thread.join(self)
        if self.exc:
            msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
            new_exc = Exception(msg)
            raise new_exc.with_traceback(self.exc[2])

def test1():
    for i in range(2):
        print('{},{}'.format(threading.current_thread().getName(), i))
    raise RuntimeError('success')
def test2():
    for i in range(5):
        print('{},{}'.format(threading.current_thread().getName(), i))
        sleep(1)

if __name__ == '__main__':

    while True:
        th = ExcThread(target=test1)
        th2 = ExcThread(target=test2)
        try:
            th.start()
            th2.start()
            th.join()
            th2.join()
        except Exception as e:
            print(e)
            break
    print('Done')

        

运行结果:

Thread-1,0
Thread-1,1
Thread-2,0
Thread 'Thread-1' threw an exception: success
Done
Thread-2,1
Thread-2,2
Thread-2,3
Thread-2,4
 

方法二:

使用类似于事件系统,出现异常之后,通知事件,然后主线程读取事件

#! python3
#-*- coding:utf-8 --
'''
Created on 2019年7月7日

@author: Administrator
'''
import threading
from time import sleep
import queue
class EventHook():
    def __init__(self):
        self.__handlers = queue.Queue()

    def __iadd__(self, handler):
        self.__handlers.put(handler)
        return self

    def __isub__(self, handler):
        self.__handlers.get()
        return self

    def fire(self, *args, **keywargs):
        if not self.__handlers.empty():
            while not self.__handlers.empty():
                print(self.__handlers.get(block = False))
            return True #如果有异常则返回True
        else:
            return False
            
exc_events = EventHook()
           
class ExcThread(threading.Thread):
    def __init__(self,group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        threading.Thread.__init__(self, group, target, name, args, kwargs)
        if kwargs is None:
            kwargs = {}
        self.__target = target
        self.__args = args
        self.__kwargs = kwargs
    def run(self):
        self.exc = None
        try:
            # Possibly throws an exception
            if self.__target:
                self.__target(*self.__args, **self.__kwargs)
        except Exception as e:
            print(e)
            global exc_events
            import sys
            exc_events += sys.exc_info()
        finally:
            del self.__target, self.__args, self.__kwargs
    
    
def test1():
    for i in range(2):
        print('{},{}'.format(threading.current_thread().getName(), i))
    raise RuntimeError('success')
def test2():
    for i in range(5):
        print('{},{}'.format(threading.current_thread().getName(), i))
        sleep(1)
    raise AttributeError('success')

if __name__ == '__main__':
    while True:
        th = ExcThread(target=test1)
        th2 = ExcThread(target=test2)
        th.start()
        th2.start()
        th.join()
        th2.join()
        if exc_events.fire():
            break
    print('Done')

        

执行结果:

Thread-1,0
Thread-1,1
success
Thread-2,0
Thread-2,1
Thread-2,2
Thread-2,3
Thread-2,4
success
(, RuntimeError('success',), )
(, AttributeError('success',), )
Done 

 

你可能感兴趣的:(python)