我觉得我对异步 IO 的理解存在差距:在较大的协程范围内将小函数包装到协程中有好处吗?这在信号方面有好处吗事件循环正确吗?这种好处的程度是否取决于包装函数是 IO 还是 CPU 绑定(bind)?
示例:我有一个协程 download()
,它:
- 通过
aiohttp
从 HTTP 端点下载 JSON 序列化字节。 - 通过
bz2.compress()
压缩这些字节- 本身不可等待 - 通过
aioboto3
将压缩字节写入S3
因此第 1 部分和第 3 部分使用这些库中的预定义协程;默认情况下,第 2 部分没有。
简单的例子:
import bz2
import io
import aiohttp
import aioboto3
async def download(endpoint, bucket_name, key):
async with aiohttp.ClientSession() as session:
async with session.request("GET", endpoint, raise_for_status=True) as resp:
raw = await resp.read() # payload (bytes)
# Yikes - isn't it bad to throw a synchronous call into the middle
# of a coroutine?
comp = bz2.compress(raw)
async with (
aioboto3.session.Session()
.resource('s3')
.Bucket(bucket_name)
) as bucket:
await bucket.upload_fileobj(io.BytesIO(comp), key)
正如上面的评论所暗示的,我的理解一直是将像 bz2.compress()
这样的同步函数放入协程中可能会弄乱它。 (即使 bz2.compress()
可能比 CPU 更受 IO 限制。)
那么,这种样板通常有什么好处吗?
async def compress(*args, **kwargs):
return bz2.compress(*args, **kwargs)
(现在 comp = await compress(raw)
在 download()
中。)
Wa-la,这现在是一个可等待的协程,因为唯一的 return
在原生协程中是有效的。是否有理由使用它?
根据 this answer ,我听说过以类似的方式随机放入 asyncio.sleep(0)
的理由 - 只是为了返回到调用协程想要中断的事件循环。这样对吗?
最佳答案
So, is there generally any benefit to this type of boilerplate?
async def compress(*args, **kwargs):
return bz2.compress(*args, **kwargs)
它没有任何好处。出乎意料,添加了一个await
doesn't guarantee控制将被传递到事件循环——只有当等待的协程实际挂起时才会发生。由于 compress
不会等待任何东西,它永远不会挂起,所以它只是名义上的协程。
请注意,在协程中添加await asyncio.sleep(0)
并不能解决问题;见this answer进行更详细的讨论。如果需要运行阻塞函数,请使用 run_in_executor
:
async def compress(*args, **kwargs):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, lambda: bz2.compress(*args, **kwargs))
关于python - 将小函数转换为协程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55857581/