为什么没有取消父协程?

时间:2016-11-01 20:24:20

标签: python python-asyncio coroutine event-loop

我正在玩Python asyncio。我的程序只有三个协程。其中两个我直接安排,而第三个安排从一个前者。我想在用户按Ctrl+C时正确完成我的程序:

import asyncio

async def coro1():
    try:
        print('coro1')
        await asyncio.sleep(1000)
    except Exception as e:
        print('coro1 exc %s' % repr(e))
        raise

async def coro2():
    try:
        print('coro2')
        await asyncio.ensure_future(coro3())
        await asyncio.sleep(1000)
    except Exception as e:
        print('coro2 exc %s' % repr(e))
        raise

async def coro3():
    try:
        print('coro3')
        await asyncio.sleep(1000)
    except Exception as e:
        print('coro3 exc %s' % repr(e))
        raise

loop = asyncio.get_event_loop()    
try:
    f1 = asyncio.ensure_future(coro1())
    f2 = asyncio.ensure_future(coro2())    
    loop.run_forever()
except KeyboardInterrupt:
    print('Exiting... Cancelling all tasks')

    f2.cancel()
    f1.cancel()

    # This code gives the same result:
    # for task in asyncio.tasks.Task.all_tasks(loop):
    #    task.cancel()

    print('Cancellation is done!')

    loop.stop()
    loop.run_forever()
finally:
    loop.close()

此代码生成下一个输出:

coro1
coro2
coro3
^CExiting... Cancelling all tasks
Cancellation is done!
coro3 exc CancelledError()
coro1 exc CancelledError()
Task was destroyed but it is pending!
task: <Task pending coro=<coro2() running at test.py:15> wait_for=<Task cancelled coro=<coro3() done, defined at test.py:23>>>

所以我想知道,为什么coro2没有被取消而coro3实际上取消了?

1 个答案:

答案 0 :(得分:2)

知道了!问题出在except块中的这两行:

# ...
loop.stop()
loop.run_forever()

由于loop.stop(),预期的取消传播无效。如果将代码更改为以下内容:

# ...

try:
    f1 = asyncio.ensure_future(coro1())
    f2 = asyncio.ensure_future(coro2())    
    loop.run_forever()
except KeyboardInterrupt:
    print('Exiting... Cancelling all tasks')
    f2.cancel()
    f1.cancel()
    print('Cancellation is done!')    
    try:
        loop.run_forever()
        # Wait a very short time for f2 cancelation and press Ctrl+C again.
    except KeyboardInterrupt:
        loop.stop()
        loop.run_forever()
finally:
    loop.close()

消息Task was destroyed but it is pending!会消失。

使用loop.run_until_complete()方法会更好一点:

f1 = asyncio.ensure_future(coro1())
f2 = asyncio.ensure_future(coro2())
tasks = asyncio.gather(f1, f2)
try:
    loop.run_until_complete(tasks)
except KeyboardInterrupt:
    print('Exiting... Cancelling all tasks')
    tasks.cancel()  # or f1.cancel(); f2.cancel()
    print('Cancellation is done!')

    loop.run_forever()
    tasks.exception()  # To skip '_GatheringFuture exception was never retrieved' warning
finally:
    loop.close()

run_until_complete添加内部回调,在完成(或取消)所有任务后停止循环。