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

Posted

技术标签:

【中文标题】asyncio,将普通函数包装为异步【英文标题】:asyncio, wrapping a normal function as asynchronous 【发布时间】:2019-08-03 08:06:24 【问题描述】:

是一个类似的函数:

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

await f(5)

适当的异步/非阻塞?

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

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

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

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

【问题讨论】:

【参考方案1】:

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

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

Asyncio 支持在单独的线程中运行传统的阻塞函数,这样它们就不会阻塞事件循环。这是通过调用run_in_executor 来实现的,它将把执行交给一个线程池(executor,用 Python 的concurrent.futures 模块的说法)并返回一个异步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() 这样的原生 asyncio 函数不要使用这种方法;它们直接告诉事件循环暂停它们以及如何唤醒它们 (source)。

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

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

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

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

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

【讨论】:

我什至不希望得到如此详尽的答案,非常感谢。 我唯一不明白的是为什么 run_in_executor 在后台使用线程?是不是完全违背了 Python 中 asyncio 的理念,因为有了 GIL,async 似乎是线程的替代方法(我什至称它为替代方法)?上下文切换开销和锁定呢?我认为在 Python 中使用 asyncio 特别有用,因为它降低了单线程环境中不必要的线程开销。 @KubaChrabański run_in_executor 使用线程,因为线程是唯一的方法来获得同步函数以与异步代码库协作。但是本机异步代码不使用 run_in_executor。 诸如 aiohttp 之类的库是使用异步函数(也称为 协程)构建的,这些函数从一开始就被设计为暂停自身而不是阻塞,然后你得到好处。这就是为什么run_in_executor 是“拐杖”,应该避免。 好的,现在明白了,“......从头开始设计”解释了一切,谢谢 @KubaChrabański 如果您想了解整个事情的工作原理,我强烈推荐 David Beazley 的 this lecture,他在前面实现了一个小但功能齐全的事件循环现场观众。该代码使用较旧的 yield from 语法,但不要让您失望,await 只是它上面的一个小语法糖,并且在引擎盖下以完全相同的方式工作。

以上是关于asyncio,将普通函数包装为异步的主要内容,如果未能解决你的问题,请参考以下文章

深入Asyncio异步生成器

我正在使用 asyncio,但异步函数正在使用 await asyncio.sleep(5) 阻塞其他异步函数

如何将 Celery 与 asyncio 结合使用?

python协程(4):asyncio

学习 asyncio:异步运行一个有延迟的函数

Python学习---Python的异步---asyncio模块(no-http)