为什么这个 asyncio.Task 永远不会完成取消?
Why does this asyncio.Task never finish cancelling?
如果我 运行 在 python3 解释器上这样做:
import asyncio
@asyncio.coroutine
def wait(n):
asyncio.sleep(n)
loop = asyncio.get_event_loop()
fut = asyncio.async(wait(10))
fut.add_done_callback(lambda x: print('Done'))
asyncio.Task.all_tasks()
我得到以下结果:
{<Task pending coro=<coro() running at /usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/coroutines.py:139> cb=[<lambda>() at <ipython-input-5-c72c2da2ffa4>:1]>}
现在如果我 运行 fut.cancel()
我得到 True
返回。但是键入 fut
returns 任务的表示是 取消:
<Task cancelling coro=<coro() running at /usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/coroutines.py:139> cb=[<lambda>() at <ipython-input-5-c72c2da2ffa4>:1]>
而且任务从未真正取消(fut.cancelled()
从未returnsTrue
)
为什么不取消?
调用task.cancel()
仅安排在事件循环的下一个运行取消任务;它不会立即取消任务,甚至不会保证在事件循环 运行 的下一次迭代时实际取消任务。这就是全部描述 in the documentation:
cancel()
Request that this task cancel itself.
This arranges for a CancelledError
to be thrown into the wrapped
coroutine on the next cycle through the event loop. The coroutine then
has a chance to clean up or even deny the request using
try/except/finally.
Unlike Future.cancel()
, this does not guarantee that the task will be
cancelled: the exception might be caught and acted upon, delaying
cancellation of the task or preventing cancellation completely. The
task may also return a value or raise a different exception.
Immediately after this method is called, cancelled()
will not return
True
(unless the task was already cancelled). A task will be marked as
cancelled when the wrapped coroutine terminates with a CancelledError
exception (even if cancel()
was not called).
在你的例子中,你实际上并没有启动事件循环,所以任务永远不会被取消。您需要调用 loop.run_until_complete(fut)
(或 loop.run_forever()
,尽管对于这种特殊情况,这并不是最佳选择),任务实际上最终会被取消。
此外,就其价值而言,使用实际脚本而不是解释器通常更容易测试 asyncio
代码,因为必须不断重写协程和 start/stop 往往会变得乏味事件循环。
在解释器中使用 asyncio 测试是棘手的,因为 python 需要保持事件循环不断轮询其任务。
所以测试 asyncio 的一些建议是:
- 编写 运行 脚本而不是使用交互式解释器
- 在脚本末尾添加
loop.run_forever()
以便执行所有任务。
- 另一种方法是 运行
loop.run_until_complete(coro())
对于每个你想要 运行 的任务。
- 在
asyncio.sleep(n)
前面加上yield from
,所以实际上可以是运行。当前代码 returns 一个生成器,什么都不做。
如果我 运行 在 python3 解释器上这样做:
import asyncio
@asyncio.coroutine
def wait(n):
asyncio.sleep(n)
loop = asyncio.get_event_loop()
fut = asyncio.async(wait(10))
fut.add_done_callback(lambda x: print('Done'))
asyncio.Task.all_tasks()
我得到以下结果:
{<Task pending coro=<coro() running at /usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/coroutines.py:139> cb=[<lambda>() at <ipython-input-5-c72c2da2ffa4>:1]>}
现在如果我 运行 fut.cancel()
我得到 True
返回。但是键入 fut
returns 任务的表示是 取消:
<Task cancelling coro=<coro() running at /usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/asyncio/coroutines.py:139> cb=[<lambda>() at <ipython-input-5-c72c2da2ffa4>:1]>
而且任务从未真正取消(fut.cancelled()
从未returnsTrue
)
为什么不取消?
调用task.cancel()
仅安排在事件循环的下一个运行取消任务;它不会立即取消任务,甚至不会保证在事件循环 运行 的下一次迭代时实际取消任务。这就是全部描述 in the documentation:
cancel()
Request that this task cancel itself.
This arranges for a
CancelledError
to be thrown into the wrapped coroutine on the next cycle through the event loop. The coroutine then has a chance to clean up or even deny the request using try/except/finally.Unlike
Future.cancel()
, this does not guarantee that the task will be cancelled: the exception might be caught and acted upon, delaying cancellation of the task or preventing cancellation completely. The task may also return a value or raise a different exception.Immediately after this method is called,
cancelled()
will not returnTrue
(unless the task was already cancelled). A task will be marked as cancelled when the wrapped coroutine terminates with aCancelledError
exception (even ifcancel()
was not called).
在你的例子中,你实际上并没有启动事件循环,所以任务永远不会被取消。您需要调用 loop.run_until_complete(fut)
(或 loop.run_forever()
,尽管对于这种特殊情况,这并不是最佳选择),任务实际上最终会被取消。
此外,就其价值而言,使用实际脚本而不是解释器通常更容易测试 asyncio
代码,因为必须不断重写协程和 start/stop 往往会变得乏味事件循环。
在解释器中使用 asyncio 测试是棘手的,因为 python 需要保持事件循环不断轮询其任务。
所以测试 asyncio 的一些建议是:
- 编写 运行 脚本而不是使用交互式解释器
- 在脚本末尾添加
loop.run_forever()
以便执行所有任务。 - 另一种方法是 运行
loop.run_until_complete(coro())
对于每个你想要 运行 的任务。 - 在
asyncio.sleep(n)
前面加上yield from
,所以实际上可以是运行。当前代码 returns 一个生成器,什么都不做。