asyncio:是否有可能取消Executor运行的未来?

时间:2014-10-16 20:47:45

标签: python executor event-loop python-asyncio

我想在Executor中使用asyncio调用loop.run_in_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

2 个答案:

答案 0 :(得分:14)

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

  

<强> cancel()

     

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

因此,取消成功的唯一时间是Executor内的任务仍然未决。现在,您实际上正在使用asyncio.Future concurrent.futures.Futureasyncio.Future实际上,loop.run_in_executor()返回的CancellationError会提升yield from如果您在调用cancel()后尝试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

{{1}}

答案 1 :(得分:1)

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

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