python3 的 asyncio 是一个功能强大的异步协程库,然后,复杂性导致学习曲线非常陡峭。与c#相比 async/await,python3 的界面 asyncio冗长且难以使用。这里总结一下使用时常见错误

常见错误一

  1. # python 异常
  2. RuntimeWarning: coroutine foo was never awaited

此警告可能在许多情况下发生,但是原因是相同的:
协程对象是通过调用 async 函数创建的,但是记录不会插入到 EventLoop
考虑以下 async 函数

  1. aysnc def foo():
  2. # do something
  3. # 没有返回值

如果要 foo() 作为异步任务调用,并且不关心结果:

  1. prepare_for_foo()
  2. foo() # RuntimeWarning: coroutine foo was never awaited
  3. remaining_work_not_depends_on_foo()

这是因为调用 foo() 实际上并不运行该函数,而是创建了一个“协程对象”。当当前EventLoop有机会时,将执行此协程对象: awaited/yield from 被调用或者所有先前的任务都已经完成

为了不执行异步任务 await,使用 loop.create_task() 具有 loop.run_until_complete()

  1. prepare_for_foo()
  2. task = loop.create_task(foo())
  3. remaining_work_not_depends_on_foo()
  4. loop.run_until_completa(task)

如果创建了 coroutine 对象并将其插入到 EventLoop 中,但是从未完成,则会出现下一个警告

常见错误二

  1. # python异常
  2. Task was destroyed but it is pending!

造成此问题的原因是 EventLoop取消待片蝗任务暂不关闭权限。 因为 Task.cancel() 安排 CancelledError在下一个循环中通过事件循环将a抛入包装的协同程序,并且协同程序然后有机会使用 try/except/finally 清理甚至拒绝请求

要正确取消所有任务并关闭 EventLoop,EventLoop应该给予最后机会运行所有已经取消但未完成的任务

合,这是取消所有任务的代码

  1. def cancel_task():
  2. # get all task in current loop
  3. tasks = Task.all_tasks()
  4. for t in tasks:
  5. t.cancel()

下面的代码正确处理任务取消和清理。 它通过调用loop.run_forever()启动EventLoop,并在接收到loop.stop()后清除任务:

  1. try:
  2. loop.run_forever() # run_forever() returns after calling loop.stop()
  3. tasks = Task.all_tasks()
  4. for t in [t for t in tasks if not (t.done() or t.cancelled())]:
  5. # give canceled tasks the last chance to run
  6. loop.run_until_complete(t)
  7. finally:
  8. loop.close()

常见错误三

  1. Task/Future is awaited in a different EventLoop than it is created

对于熟悉C#async / await的人来说,这个错误尤其令人惊讶。 这是因为大部分asyncio都不是线程安全的,也不是asyncio.Future或asyncio.Task。 另外,不要将asyncio.Future与concurrent.futures.Future混淆,因为它们不兼容(至少在Python 3.6之前):后者是线程安全的,而前者不是。
为了在不同的线程中等待asyncio.Future,asyncio.Future可以包装在concurrent.Future中:

  1. def wrap_future(asyncio_future):
  2. def done_callback(af, cf):
  3. try:
  4. cf.set_result(af.result())
  5. except Exception as e:
  6. acf.set_exception(e)
  7. concur_future = concurrent.Future()
  8. asyncio_future.add_done_callback(
  9. lambda f: done_callback(f, cf=concur_future))
  10. return concur_future

参考

https://xinhuang.github.io/posts/2017-07-31-common-mistakes-using-python3-asyncio.html