Title Loading...
- Time Loading...
- Wordcount Loading...
Catalogue
Python使用concurrent.futures构建线程池时的异常捕获与处理
最近在完善 teelebot 的多线程机制时,用到了 Python模块 concurrent.futures
的 ThreadPoolExecutor
类。通过 ThreadPoolExecutor
构建线程池非常简单:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(5) as thread_pool:
thread_pool.submit(function, argument)
...
但是,这样去使用,并不会捕获子线程中的异常。也就是说,若 function 在执行过程中抛出了错误,并不会被主线程捕获后输出到命令行,而是看上去一切正常。后来查看文档,若想捕获子线程的异常,应当使用 exception(timeout=None)
:
...
fur = thread_pool.submit(function, argument)
print(fur.exception())
但直接使用 exception
,会阻塞主线程,并等待调用执行完毕。若想以非阻塞的形式捕获,则需要结合使用 add_done_callback(fn)
。add_done_callback(fn)
的作用是,当线程被取消或者结束运行时,调用add_done_callback
的唯一参数 fn
,fn
是一个可调用的对象,可以是一个函数。结合 add_done_callback
的代码如下:
from concurrent.futures import ThreadPoolExecutor
def exception_callback(fur):
if fur.exception() != None: #若线程正常退出,返回None
print(fur.exception())
with ThreadPoolExecutor(5) as thread_pool:
fur = thread_pool.submit(function, argument)
fur.add_done_callback(exception_callback)
这样就能成功捕获并输出子线程的异常。但这样输出仍然有一个问题,就是报错信息过于简单,以 NameError
为例,只有简短的一句话:
NameError: name 'i' is not defined
并不会给出具体的文件,出错的代码和行数,是非常不利于调试的。若想输出详细的报错,则需要使用到另外一个方法:result(timeout=None)
。result
的作用是返回调用返回的值,它也会阻塞主线程。 结合 result
的最终示例代码如下:
# -*- coding:utf-8 -*-
from concurrent.futures import ThreadPoolExecutor
def do_something(m):
print(m)
print(i) #打印并不存在的i,模拟异常
def exception_callback(fur):
if fur.exception() != None: #若线程正常退出,返回None
print(fur.result()) #判断是否存在异常,存在则打印返回的值
if __name__ == "__main__":
with ThreadPoolExecutor(5) as thread_pool:
fur = thread_pool.submit(do_something, "hello python!")
fur.add_done_callback(exception_callback)
输出:
hello python!
exception calling callback for <Future at 0x23c90dd88e0 state=finished raised NameError>
Traceback (most recent call last):
File "C:\Users\UserName\AppData\Local\Programs\Python\Python38\lib\concurrent\futures\_base.py", line 328, in _invoke_callbacks
callback(self)
File "C:\Users\UserName\Desktop\futures.py", line 10, in exception_callback
print(fur.result())
File "C:\Users\UserName\AppData\Local\Programs\Python\Python38\lib\concurrent\futures\_base.py", line 432, in result
return self.__get_result()
File "C:\Users\UserName\AppData\Local\Programs\Python\Python38\lib\concurrent\futures\_base.py", line 388, in __get_result
raise self._exception
File "C:\Users\UserName\AppData\Local\Programs\Python\Python38\lib\concurrent\futures\thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "C:\Users\UserName\Desktop\futures.py", line 6, in do_something
print(i) #打印并不存在的i,模拟异常
NameError: name 'i' is not defined