为什么这个 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 的一些建议是:

  1. 编写 运行 脚本而不是使用交互式解释器
  2. 在脚本末尾添加 loop.run_forever() 以便执行所有任务。
  3. 另一种方法是 运行 loop.run_until_complete(coro()) 对于每个你想要 运行 的任务。
  4. asyncio.sleep(n)前面加上yield from,所以实际上可以是运行。当前代码 returns 一个生成器,什么都不做。