从异步函数调用普通函数与从异步函数调用协程有什么区别?

sha*_*aka 6 python async-await python-asyncio

  1. async def caller():
        await bar()
        print("finish")
    
    async def bar():
       // some code here
    
    Run Code Online (Sandbox Code Playgroud)
  2. async def caller():
        bar()
        print("finish")
    
    def bar():
       //some code here
    
    Run Code Online (Sandbox Code Playgroud)

在上面的例子中。两种情况下,调用者都必须等待 bar() 完成。在这种情况下,bar 成为普通/协程有什么不同吗?如果我们想“等待”某些函数,为什么不使用普通函数。

use*_*342 7

不同之处在于,第二个示例bar()是一个非异步函数,因此它本身不能等待任何内容。例如,如果您想从内部访问 Web 服务bar(),那么在第一个示例中这不会有问题,您只需使用aiohttp即可。在第二个示例中,这几乎是不可能的,因为异步库需要从异步函数中使用,而非异步库将在等待响应时阻塞整个事件循环。

如果我们想“等待”某些函数,为什么不直接使用普通函数呢?

如果您等待的函数不需要与外界通信(例如,如果它只是在字典中打乱数据),那么它可以而且应该是一个普通函数。另一方面,如果需要做IO,它应该是一个异步函数。


dec*_*eze 2

如果它不是一个async函数,那么显然你不需要await它。并非您在async函数内调用的每个函数都必须async也必须经过awaited;您可以从函数内调用常规非异步函数async

\n

整个 asyncio 模型围绕事件循环工作。任一时间只能运行一项任务,事件循环会协调当前正在运行的任务。函数await内部会暂停该函数的执行,并允许另一个任务在事件循环上运行。所以,在这个例子中:

\n
async def caller():\n    await bar()\n    print('finish')\n
Run Code Online (Sandbox Code Playgroud)\n

执行过程是这样的:

\n
    \n
  1. caller()在事件循环上调用并安排,一旦存在可用性就会执行它。
  2. \n
  3. 它调用bar(),后者在事件循环上安排其执行。
  4. \n
  5. 暂停await执行caller.
  6. \n
  7. 事件循环执行bar;假设它正在发出网络请求,因此在响应返回之前不会发生任何事情,事件循环可以自由运行任何其他计划的异步任务\xe2\x80\xa6
  8. \n
  9. 网络响应返回,事件循环恢复执行bar
  10. \n
  11. bar结束后,事件循环继续执行caller.
  12. \n
\n

await存在是为了协调异步任务的运行顺序以及哪个任务取决于哪个其他任务的结果。

\n

  • 此外,“await”不一定会暂停执行,它只是开始执行异步代码,在本例中是协程对象,_允许_它暂停。无法保证暂停,这偶尔会成为[bug](/sf/answers/3417142361/)的来源。 (3认同)
  • @SyedSaad 正确。不等待任何内容的异步函数具有强烈的“代码味道”,表明它应该切换到异步 API ([`run_in_executor`](https://docs.python.org/3/library/asyncio-eventloop. html#asyncio.loop.run_in_executor)如果它正在与旧的阻塞代码对话)或者它不应该一开始就将自己宣传为异步。 (3认同)