concurrent.futures.ThreadPoolExecutor吞噬异常(Python 3.6)

19

我试图在Windows 7上使用Python 3.6中的ThreadPoolExecutor,但似乎异常被静默忽略或停止程序执行。以下是示例代码:

#!/usr/bin/env python3

from time import sleep

from concurrent.futures import ThreadPoolExecutor

EXECUTOR = ThreadPoolExecutor(2)


def run_jobs():
    EXECUTOR.submit(some_long_task1)
    EXECUTOR.submit(some_long_task2, 'hello', 123)
    return 'Two jobs was launched in background!'


def some_long_task1():
    print("Task #1 started!")
    for i in range(10000000):
        j = i + 1
    1/0
    print("Task #1 is done!")


def some_long_task2(arg1, arg2):
    print("Task #2 started with args: %s %s!" % (arg1, arg2))
    for i in range(10000000):
        j = i + 1
    print("Task #2 is done!")


if __name__ == '__main__':
    run_jobs()
    while True:
        sleep(1)

输出结果:

Task #1 started!
Task #2 started with args: hello 123!
Task #2 is done!

我要按下Ctrl+C才能终止它的运行。

然而,当我从some_long_task1函数中移除1/0的代码时,任务#1就可以正常完成:

Task #1 started!
Task #2 started with args: hello 123!
Task #1 is done!
Task #2 is done!

我需要以某种方式捕获在运行于 ThreadPoolExecutor 中的函数引发的异常。

Python 3.6(Minconda),Windows 7 x64。

3个回答

28

ThreadPoolExecutor.submit返回一个future对象,它代表了计算结果一旦可用时的结果。为了不忽略作业引发的异常,您需要实际访问此结果。首先,您可以更改run_job以返回创建的future对象:

def run_jobs():
    fut1 = EXECUTOR.submit(some_long_task1)
    fut2 = EXECUTOR.submit(some_long_task2, 'hello', 123)
    return fut1, fut2

然后,让顶层代码等待 futures 完成,并访问它们的结果:

import concurrent.futures

if __name__ == '__main__':
    futures = run_jobs()
    concurrent.futures.wait(futures)
    for fut in futures:
        print(fut.result())

当对一个执行出现异常的future调用result()时,将会将异常传播给调用者。在这种情况下,ZeroDivisionError将在顶层引发。


2

您可以使用 try 语句来处理异常。以下是您的 some_long_task1 方法可能会如何:

def some_long_task1():
    print("Task #1 started!")
    try:
        for i in range(10000000):
            j = i + 1
        1/0
    except Exception as exc:
        print('some_long_task1 generated an exception: {}'.format(exc))
    print("Task #1 is done!")

当该方法在您的脚本中使用时的输出:

Task #1 started!
Task #2 started with args: hello 123!
some_long_task1 generated an exception: integer division or modulo by zero
Task #1 is done!
Task #2 is done!
(the last while loop running...)

5
这并不真正回答问题,因为问题针对的是捕获子线程异常,从 Q 描述的性质来看,似乎理解 try: catch: 并不是这里的问题,因为提问者已经在询问如何特别地捕捉异常。 - jave.web

1

如之前的答案所示,捕获 ThreadPoolExecutor 异常有两种方法 - 在 future 中检查异常或记录它们。这都取决于一个人想要处理潜在的异常。一般来说,我的经验法则包括:

  1. 如果我只想要记录错误,包括跟踪堆栈。log.exception 是更好的选择。需要通过 future.exception() 添加额外的逻辑才能记录相同数量的信息。
  2. 如果代码需要在主线程中以不同的方式处理不同的异常。检查 future 的状态是正确的方法。此外,在这种情况下,您可能会发现 as_completed 函数也很有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接