asyncio,将普通函数包装为异步

Kub*_*ski 11 asynchronous wrapper python-3.x python-requests python-asyncio

是一个函数,如:

async def f(x):
    time.sleep(x)

await f(5)
Run Code Online (Sandbox Code Playgroud)

正确异步/非阻塞?

asyncio 提供的睡眠功能有什么不同吗?

最后,aiorequests 是请求的可行异步替代品吗?

(在我看来,它基本上将主要组件包装为异步)

https://github.com/pohmelie/aiorequests/blob/master/aiorequests.py

use*_*342 15

提供的函数不是正确编写的异步函数,因为它调用了asyncio 中禁止的阻塞调用。(“协程”有问题的一个快速提示是它不包含单个await。)被禁止的原因是诸如此类的阻塞调用sleep()将暂停当前线程而不给其他协程机会运行. 换句话说,不是暂停当前协程,而是暂停整个事件循环,即所有协程。

在 asyncio(和其他异步框架)中,阻塞原语之类的time.sleep()被替换为 awaitables 之类的asyncio.sleep(),它们暂停等待者并在适当的时候恢复它。其他协程和事件循环不仅不受协程挂起的影响,而且正是它们有机会运行的时候。协程的暂停和恢复是 async-await 协同多任务的核心。

Asyncio 支持在单独的线程中运行遗留阻塞函数,因此它们不会阻塞事件循环。这是通过调用run_in_executorwhich 将执行移交给线程池(Python模块的说法中的executorconcurrent.futures)并返回 asyncio awaitable 来实现的

async def f(x):
    loop = asyncio.get_event_loop()
    # start time.sleep(x) in a separate thread, suspend
    # the current coroutine, and resume when it's done
    await loop.run_in_executor(time.sleep, x)
Run Code Online (Sandbox Code Playgroud)

这是 aiorequests 用来包装请求的阻塞函数的技术。原生 asyncio 函数asyncio.sleep() 不使用这种方法;它们直接告诉事件循环挂起它们以及如何唤醒它们(来源)。

run_in_executor对于快速包装遗留阻塞代码非常有用和有效,除此之外别无他法。由于以下几个原因,它始终不如本地异步实现:

  • 它不实现取消。与线程不同,asyncio 任务是完全可取消的,但这并没有扩展到run_in_executor,它具有线程的局限性。

  • 它不提供可能数以万计并并行运行的轻量级任务。run_in_executor在引擎盖下使用线程池,因此如果您等待的函数超过最大工作线程数,则某些函数将不得不等待轮到它们甚至开始工作。另一种方法是增加工作线程的数量,这将使操作系统淹没在过多线程中。Asyncio 允许并行操作的数量与您在手写状态机中poll用于侦听事件的数量相匹配。

  • 它可能与更复杂的 API 不兼容,例如那些公开用户提供的回调、迭代器或提供自己的基于线程的异步功能的 API。

建议避免像 aiorequests 这样的拐杖,直接进入aiohttp。API 与请求的 API 非常相似,使用起来也几乎一样愉快。

  • 我什至不希望得到如此详尽的答案,非常感谢。 (2认同)
  • 好的,现在明白了,“......这是从头开始设计的”解释了一切,谢谢 (2认同)
  • @KubaChrabański 如果你想了解整个事情**如何运作**,我强烈推荐 David Beazley 的[这个讲座](https://www.youtube.com/watch?v=MCs5OvhV9S4),他在其中实现了一个小但在现场观众面前功能齐全的事件循环。该代码使用较旧的“yield from”语法,但不要让这让您失望,“await”只是它的一个微小的语法糖,并且在幕后以完全相同的方式工作。 (2认同)
  • @smwikipedia 回复主题,我现在明白你在说什么了。是的,您应该考虑在不同任务中由“run_in_executor()”运行的函数与在阻塞代码中从不同线程运行它们相同。是的,“从头开始”,但这基本上已经发生了。Asyncio 不再是一个新事物,大多数现代网络库都是异步感知的。 (2认同)
  • @smwikipedia 我现在才注意到最后一个问题。因此,“run_in_executor”*本身*不需要是线程安全的,因为它始终从事件循环线程运行。根据定义,“传递”给“run_in_executor”的函数必须是线程安全的,因为它将在主线程之外运行,但其线程安全级别将取决于它实际执行的操作。(例如,如果它只是打开一个文件并从中读取,那么它可能永远不需要执行任何显式锁定。) (2认同)