【问题标题】:Timeout in concurrent.futures.ThreadPoolExecutor within context manager not work correctly上下文管理器中的 concurrent.futures.ThreadPoolExecutor 超时无法正常工作
【发布时间】:2019-04-27 09:28:30
【问题描述】:

有人可以帮我解释为什么当我在上下文管理器中使用超时时超时不能正常工作吗?

它在不使用上下文管理器的情况下正常工作,它会在 5 秒后引发 TimeoutException,但使用上下文管理器它不会在 5 秒后引发异常。

没有上下文管理器的实现

from concurrent import futures
MAX_WORKERS = 20

def download_one(c):
    import time
    time.sleep(100)


def download_many():
    executor = futures.ThreadPoolExecutor(MAX_WORKERS)
    res = executor.map(download_one, [1,2,3,4],timeout=5)
    print(list(res))

    return len(list(res))


download_many()

使用上下文管理器实现

from concurrent import futures
MAX_WORKERS = 20

def download_one(c):
    import time
    time.sleep(100)


def download_many():
    with futures.ThreadPoolExecutor(MAX_WORKERS) as executor:
        res = executor.map(download_one, [1,2,3,4],timeout=5)
        print(list(res))

    return len(list(res))

download_many()

【问题讨论】:

  • 实际上,在这两种情况下都会引发concurrent.futures._base.TimeoutError。但是,在第二个示例中,它是在所有线程完成后打印的。减少您的超时时间(或等待更长时间)以查看它。
  • 我已经阅读了您链接到的线程,但这与我的情况并不重复。这两个例子都引发了 TimeoutError 但我不明白为什么在所有线程完成后引发 second 。除了在上下文管理器中退出上下文管理器时将调用 excecutor.shutdown(wait=True) 之外,一切都是相同的。
  • 我认为这是相同的行为,但可能很难理解(多线程和异常有时可能很困难)。这当然是一个有趣的问题!我制定了一个解释/演示行为的答案。我希望它能回答你的问题。

标签: python-3.x python-multithreading threadpoolexecutor


【解决方案1】:

超时在这两种情况下都能正常工作。 concurrent.futures._base.TimeoutError 在调用 list(res) 后 5 秒(= 指定的超时值)引发。但是,当使用上下文管理器(with 语句)时,会调用上下文管理器的 __exit__ 方法。在这种情况下,它将等到所有线程都完成后再离开上下文并(重新)引发原始错误。

这可以通过在正确的位置捕获和记录异常来证明:

import concurrent
import logging
import time
from concurrent import futures

MAX_WORKERS = 20


def download_one(c):
    logging.info('download_one(%s)' % str(c))
    time.sleep(10)  # Note: reduced to 10 seconds


def sub(executor):
    try:
        futures = executor.map(download_one, [1,2,3,4], timeout=5)
    except concurrent.futures._base.TimeoutError:
        logging.info('map timed out!')  # this is never logged
        raise

    try:
        results = list(futures)
    except concurrent.futures._base.TimeoutError:
        logging.info('list timed out!')  # here it happens!
        raise

    logging.info(results)
    logging.info('sub done')
    return len(result)


def download_many1():  # without context manager
    logging.info('download_many1')
    executor = futures.ThreadPoolExecutor(MAX_WORKERS)
    return sub(executor)


def download_many2():  # with context manager
    logging.info('download_many2')
    with futures.ThreadPoolExecutor(MAX_WORKERS) as executor:
        return sub(executor)


logging.basicConfig(level=logging.DEBUG, format='%(asctime)-15s   %(message)s')

logging.info('start 1')
try:
    download_many1()
except concurrent.futures._base.TimeoutError:
    logging.info('timeout!')
finally:
    logging.info('1 finished\n')

logging.info('start 2')
try:
    download_many2()
except concurrent.futures._base.TimeoutError:
    logging.info('timeout!')
finally:
    logging.info('2 finished\n')

这个输出:

2019-04-27 21:17:20,578   start 1
2019-04-27 21:17:20,578   download_many1
2019-04-27 21:17:20,578   download_one(1)
2019-04-27 21:17:20,578   download_one(2)
2019-04-27 21:17:20,578   download_one(3)
2019-04-27 21:17:20,578   download_one(4)
2019-04-27 21:17:25,593   list timed out!   # actual timeout after 5 seconds
2019-04-27 21:17:25,593   timeout!          # the timeout you see at the same time
2019-04-27 21:17:25,593   1 finished

2019-04-27 21:17:25,593   start 2
2019-04-27 21:17:25,593   download_many2
2019-04-27 21:17:25,593   download_one(1)
2019-04-27 21:17:25,593   download_one(2)
2019-04-27 21:17:25,593   download_one(3)
2019-04-27 21:17:25,593   download_one(4)
2019-04-27 21:17:30,610   list timed out!   # actual timeout after 5 seconds
2019-04-27 21:17:35,608   timeout!          # the timeout you see 5 seconds later!!
2019-04-27 21:17:35,608   2 finished

【讨论】:

    猜你喜欢
    • 2022-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-18
    • 1970-01-01
    • 2012-05-16
    • 2021-05-23
    • 1970-01-01
    相关资源
    最近更新 更多