我想在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
答案 0 :(得分:14)
在这种情况下,一旦实际开始运行,就无法取消Future
,因为您依赖于concurrent.futures.Future
和its docs state the following的行为:
<强>
cancel()
强>尝试取消通话。 如果当前正在执行呼叫 并且无法取消,否则该方法将返回
False
,否则 该呼叫将被取消,该方法将返回True
。
因此,取消成功的唯一时间是Executor
内的任务仍然未决。现在,您实际上正在使用asyncio.Future
concurrent.futures.Future
,asyncio.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()