问题原因:
一个线程拥有自己独立的上下文和调用栈,如果该线程(调用栈)中 抛出异常之后,调用方是无法捕获的。
解决方法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
(
(
Done