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

asyncio, wrapping a normal function as asynchronous

是一个像这样的函数:

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

await f(5)

正确asynchronous/non-blocking?

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

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

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

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

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

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

Asyncio 在单独的线程中支持 运行ning 遗留阻塞函数,这样它们就不会阻塞事件循环。这是通过调用 run_in_executor which will hand off the execution to a thread pool (executor in the parlance of Python's concurrent.futures 模块)和 return 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)

这是 aiorequests 用来包装请求的阻塞函数的技术。 asyncio.sleep() 等本机异步函数不使用此方法;他们直接告诉事件循环暂停它们以及如何唤醒它们 (source)。

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

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

  • 不提供数以万计的运行并行的轻量级任务。 run_in_executor 在后台使用线程池,因此如果您等待的函数数量超过最大工作线程数,则某些函数甚至必须等待轮到它们才能开始工作。另一种方法是增加 worker 的数量,这会使 OS 被太多的线程淹没。 Asyncio 允许并行操作的数量与您在使用 poll 侦听事件的手写状态机中拥有的数量相匹配。

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

建议避开aiorequests之类的拐杖,直接潜入aiohttp。 API 与请求非常相似,使用起来几乎一样愉快。