concurrent.futures.ThreadPoolExecutor проглатывает исключения (Python 3.6)

Я пытаюсь использовать ThreadPoolExecutor в Python 3.6 в Windows 7, и мне кажется, что исключения игнорируются или останавливают выполнение программы. Пример кода:

#!/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.

Однако, когда я удаляю 1/0 из some_long_task1, задача № 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.


person LetMeSOThat4U    schedule 16.03.2018    source источник


Ответы (3)


ThreadPoolExecutor.submit возвращает future объект, представляющий результат вычисления, как только он станет доступен. Чтобы не игнорировать исключения, вызванные заданием, вам необходимо получить доступ к этому результату. Во-первых, вы можете изменить 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())

Вызов result() в будущем, выполнение которого вызвало исключение, распространит исключение на вызывающую сторону. В этом случае ZeroDivisionError будет повышен на верхнем уровне.

person user4815162342    schedule 16.03.2018

Вы можете обрабатывать исключения с помощью инструкции 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...)
person arudzinska    schedule 16.03.2018
comment
Это на самом деле не отвечает на вопрос, поскольку вопрос нацелен на перехват исключения подпотока, и из-за характера описания Q это не похоже на понимание try: catch: здесь проблема, поскольку OP конкретно спрашивает об отлове исключений уже. - person jave.web; 01.03.2021

Как указывалось в предыдущих ответах, есть два способа перехватить исключения для ThreadPoolExecutor - проверить исключение в future или зарегистрировать их. Все зависит от того, что нужно делать с потенциальными исключениями. В общем, мое эмпирическое правило включает:

  1. Если я только хочу записать ошибку, включая стек трассировки. log.exception - лучший выбор. Необходимо добавить дополнительную логику для записи того же количества информации через future.exception().
  2. Если код должен обрабатывать разные исключения по-разному в основном потоке. Проверка статуса future - лучший способ. Кроме того, вы можете найти функцию as_completed также полезен в этом случае.
person Chris.Q    schedule 13.03.2021