简体   繁体   English

如何在未来运行时立即退出带有语句的ThreadPoolExecutor

[英]How to exit ThreadPoolExecutor with statement immediately when a future is running

Coming from a.Net background I am trying to understand python multithreading using concurrent.futures.ThreadPoolExecutor and submit .来自.Net 背景我试图理解 python 多线程使用concurrent.futures.ThreadPoolExecutorsubmit I was trying to add a timeout to some code for a test but have realised I don't exactly understand some elements of what I'm trying to do.我试图为一些测试代码添加超时,但我意识到我并不完全理解我正在尝试做的事情的某些元素。 I have put some simplified code below.我在下面放了一些简化的代码。 I would expect the method to return after around 5 seconds, when the call to concurrent.futures.wait(futures, return_when=FIRST_COMPLETED) completes.当对concurrent.futures.wait(futures, return_when=FIRST_COMPLETED)的调用完成时,我希望该方法在大约 5 秒后返回。 In fact it takes the full 10 seconds.事实上,它需要整整 10 秒。 I suspect it has to do with my understanding of the with statement as changing the code to thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=2) results in the behvaiour I would expect.我怀疑这与我对with语句的理解有关,因为将代码更改为thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=2)会导致我期望的行为。 Adding a call to the shutdown method doesn't do anything as all the futures are already running.添加对shutdown方法的调用不会执行任何操作,因为所有期货都已经在运行。 Is there a way to exit out of the with statement immediately following the call to wait ?有没有办法在调用wait后立即退出with语句? I have tried using break and return but they have no effect.我试过使用breakreturn但它们没有效果。 I am using python 3.10.8我正在使用 python 3.10.8

from concurrent.futures import FIRST_COMPLETED
import threading
import concurrent
import time

def test_multiple_threads():
    set_timeout_on_method()
    print("Current Time =", datetime.now()) # Prints time N + 10

  

def set_timeout_on_method():
    futures = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as thread_pool:
        print("Current Time =", datetime.now()) # Prints time N
        futures.append(thread_pool.submit(time.sleep, 5))
        futures.append(thread_pool.submit(time.sleep, 10))
        concurrent.futures.wait(futures, return_when=FIRST_COMPLETED)
        print("Current Time =", datetime.now()) # Prints time N + 5
    print("Current Time =", datetime.now()) # Prints time N + 10

AFAIK, there is no native way to terminate threads from ThreadPoolExecutor and it's supposedly not even a good idea, as described in existing answers ( exhibit A , exhibit B ).据我所知,没有从ThreadPoolExecutor终止线程的本机方法,而且它甚至不是一个好主意,如现有答案中所述( 展示 A展示 B )。

It is possible to do this with processes in ProcessPoolExecutor , but even then the main process would apparently wait for all the processes that already started :可以使用ProcessPoolExecutor中的进程执行此操作,但即便如此,主进程显然会等待所有已经启动的进程

If wait is False then this method will return immediately and the resources associated with the executor will be freed when all pending futures are done executing.如果 wait 为 False 则此方法将立即返回,并且在所有未决期货执行完毕后将释放与执行程序关联的资源。 Regardless of the value of wait, the entire Python program will not exit until all pending futures are done executing.不管 wait 的值是多少,整个 Python 程序都不会退出,直到所有挂起的 futures 都执行完毕。

This means that even though the "End @" would be printed after cca 5 seconds, the script would terminate after cca 20 seconds.这意味着即使在 cca 5 秒后打印“End @”,脚本也会在 cca 20 秒后终止。

from concurrent.futures import FIRST_COMPLETED, ProcessPoolExecutor, wait
from datetime import datetime
from time import sleep

def multiple_processes():
    print("Start @", datetime.now())
    set_timeout_on_method()
    print("End @", datetime.now())

def set_timeout_on_method():
    futures = []

    with ProcessPoolExecutor() as executor:
        futures.append(executor.submit(sleep, 5))
        futures.append(executor.submit(sleep, 10))
        futures.append(executor.submit(sleep, 20))
        print("Futures created @", datetime.now())

        if wait(futures, return_when=FIRST_COMPLETED):
            print("Shortest future completed @", datetime.now())
            executor.shutdown(wait=False, cancel_futures=True)

if __name__ == "__main__":
    multiple_processes()

With max_workers set to 1 , the entire script would take cca 35 seconds because (to my surprise) the last future doesn't get cancelled, despite cancel_futures=True .max_workers设置为1时,整个脚本将花费大约 35 秒,因为(令我惊讶的是)尽管cancel_futures=True ,最后一个未来并没有被取消。

You could kill the workers, though.不过,你可以杀死工人。 This would make the main process finish without delay:这将使主进程立即完成:

...
    with ProcessPoolExecutor(max_workers=1) as executor:
        futures.append(executor.submit(sleep, 5))
        futures.append(executor.submit(sleep, 10))
        futures.append(executor.submit(sleep, 20))
        print("Futures created @", datetime.now())

        if wait(futures, return_when=FIRST_COMPLETED):
            print("Shortest future completed @", datetime.now())
            subprocesses = [p.pid for p in executor._processes.values()]
            executor.shutdown(wait=False, cancel_futures=True)

    for pid in subprocesses:
        os.kill(pid, signal.SIGTERM)
...

Disclaimer : Please don't take this answer as an advice to whatever you are trying achieve.免责声明:请不要将此答案作为对您尝试实现的目标的建议。 It's just a brainstorming based on your code.这只是基于您的代码的头脑风暴。

The problem is that you can not cancel Future if it was already started:问题是你不能取消Future如果它已经启动:

Attempt to cancel the call. 尝试取消呼叫。 If the call is currently being executed or finished running and cannot be cancelled then the method will return False, otherwise the call will be cancelled and the method will return True. 如果调用当前正在执行或已完成运行且无法取消,则该方法将返回 False,否则调用将被取消并且该方法将返回 True。

To prove it I made the following changes:为了证明这一点,我做了以下更改:

from concurrent.futures import (
    FIRST_COMPLETED,
    ThreadPoolExecutor,
    wait as futures_wait,
)
from time import sleep
from datetime import datetime


def test_multiple_threads():
    set_timeout_on_method()
    print("Current Time =", datetime.now())  # Prints time N + 10


def set_timeout_on_method():
    with ThreadPoolExecutor(max_workers=2) as thread_pool:
        print("Current Time =", datetime.now())  # Prints time N
        futures = [thread_pool.submit(sleep, t) for t in (2, 10, 2, 100, 100, 100, 100, 100)]
        futures_wait(futures, return_when=FIRST_COMPLETED)
        print("Current Time =", datetime.now())  # Prints time N + 5
        print([i.cancel() if not i.done() else "DONE" for i in futures])
    print("Current Time =", datetime.now())  # Prints time N + 10


if __name__ == '__main__':
    test_multiple_threads()

As you can see only three of tasks are done.如您所见,只完成了三个任务。 ThreadPoolExecutor actually based on threading module and Thread in Python can't be stopped in some conventional way. ThreadPoolExecutor实际上是基于threading模块和 Python 中的Thread不能以一些常规方式停止。 Check this answer检查这个答案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM