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 与请求非常相似,使用起来几乎一样愉快。
是一个像这样的函数:
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 与请求非常相似,使用起来几乎一样愉快。