【问题标题】:asyncio: Is it possible to cancel a future been run by an Executor?asyncio:是否可以取消由 Executor 运行的未来?
【发布时间】:2014-10-16 20:47:45
【问题描述】:

我想使用异步调用 loop.run_in_executor 在 Executor 中启动一个阻塞函数,然后稍后取消它,但这似乎对我不起作用。

代码如下:

import asyncio
import time

from concurrent.futures import ThreadPoolExecutor


def blocking_func(seconds_to_block):
    for i in range(seconds_to_block):
        print('blocking {}/{}'.format(i, seconds_to_block))
        time.sleep(1)

    print('done blocking {}'.format(seconds_to_block))


@asyncio.coroutine
def non_blocking_func(seconds):
    for i in range(seconds):
        print('yielding {}/{}'.format(i, seconds))
        yield from asyncio.sleep(1)

    print('done non blocking {}'.format(seconds))


@asyncio.coroutine
def main():
    non_blocking_futures = [non_blocking_func(x) for x in range(1, 4)]
    blocking_future = loop.run_in_executor(None, blocking_func, 5)
    print('wait a few seconds!')
    yield from asyncio.sleep(1.5)

    blocking_future.cancel()
    yield from asyncio.wait(non_blocking_futures)



loop = asyncio.get_event_loop()
executor = ThreadPoolExecutor(max_workers=1)
loop.set_default_executor(executor)
asyncio.async(main())
loop.run_forever()

我希望上面的代码只允许阻塞函数输出:

blocking 0/5
blocking 1/5

然后查看非阻塞函数的输出。但相反,即使在我取消之后,阻塞的未来也会继续。

有可能吗?还有其他方法吗?

谢谢

编辑:更多关于使用 asyncio 运行阻塞和非阻塞代码的讨论:How to interface blocking and non-blocking code with asyncio

【问题讨论】:

    标签: python executor event-loop python-asyncio


    【解决方案1】:

    在这种情况下,一旦Future 实际开始运行,就无法取消它,因为您依赖于concurrent.futures.Future 和its docs state the following 的行为:

    cancel()

    尝试取消通话。 如果当前正在执行调用 并且无法取消则该方法将返回False,否则 调用将被取消,方法将返回True。

    因此,取消成功的唯一情况是任务在Executor 内仍处于挂起状态。现在,您实际上使用的是包裹在concurrent.futures.Future 周围的asyncio.Future,实际上,如果您在调用yield from 后尝试yield from,loop.run_in_executor() 返回的asyncio.Future 将引发CancellationError ,即使底层任务实际上已经在运行。但是,它实际上不会取消Executor内任务的执行。

    如果您需要实际取消任务,则需要使用更常规的方法来中断线程中运行的任务。你如何做到这一点的细节取决于用例。对于您在示例中提供的用例,您可以使用threading.Event:

    def blocking_func(seconds_to_block, event):
        for i in range(seconds_to_block):
            if event.is_set():
                return
            print('blocking {}/{}'.format(i, seconds_to_block))
            time.sleep(1)
    
        print('done blocking {}'.format(seconds_to_block))
    
    
    ...
    event = threading.Event()
    blocking_future = loop.run_in_executor(None, blocking_func, 5, event)
    print('wait a few seconds!')
    yield from asyncio.sleep(1.5)
    
    blocking_future.cancel()  # Mark Future as cancelled
    event.set() # Actually interrupt blocking_func
    

    【讨论】:

    • 感谢您的回复。您建议的解决方案听起来很适合解决这种情况。我的问题是,blocking_func 在我的真实案例中属于第三方。我将等待一两天,看看是否有人有任何进一步的建议;否则我会将您的答案标记为已接受。
    • 你知道如何在完成时向blocking_func添加回调吗?
    【解决方案2】:

    由于线程共享同一个进程的内存地址空间,因此没有安全的方法来终止正在运行的线程。这就是为什么大多数编程语言不允许杀死正在运行的线程的原因(围绕这个限制有很多丑陋的黑客攻击)。

    Java 通过 hard way 学习它。

    解决方案包括在单独的进程而不是线程中运行您的函数并优雅地终止它。

    Pebble 库提供类似于concurrent.futures 的接口,支持取消运行Futures。

    from pebble import ProcessPool
    
    def function(foo, bar=0):
        return foo + bar
    
    with ProcessPool() as pool:
        future = pool.schedule(function, args=[1])
    
        # if running, the container process will be terminated 
        # a new process will be started consuming the next task
        future.cancel()  
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-23
      • 1970-01-01
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 2021-03-01
      相关资源
      最近更新 更多