【问题标题】:concurrent.futures.ThreadPoolExecutor swallowing exceptions (Python 3.6)concurrent.futures.ThreadPoolExecutor 吞下异常(Python 3.6)
【发布时间】:2018-08-25 14:57:21
【问题描述】:

我正在尝试在 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。

【问题讨论】:

    标签: python python-3.x windows-7-x64 python-multithreading cpython


    【解决方案1】:

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

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

    然后,为期货完成顶级代码wait,并访问其结果:

    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...)
      

      【讨论】:

      • 这并没有真正回答这个问题,因为该问题的目标是捕获子线程的异常,并且从 Q 描述的性质来看,它似乎并不理解 try: catch: 是这里的问题,因为OP 已经在询问是否专门捕获异常。
      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-03-08
        • 1970-01-01
        • 2010-11-01
        • 2018-05-26
        • 2012-03-06
        • 2012-06-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多