增强同步软件API以允许异步使用

时间:2018-11-28 14:28:43

标签: python asynchronous refactoring python-asyncio

我在Python 3.5+中有一个模块,提供了一个从远程Web API读取一些数据并将其返回的功能。该函数依赖于包装器函数,后者使用库requests进行HTTP调用。

在这里(故意删除所有数据验证逻辑和异常处理):

# module fetcher.py

import requests

# high-level module API
def read(some_params):
    resp = requests.get('http://example.com', params=some_params)
    return resp.json()

# wrapper for the actual remote API call
def get_data(some_params):
    return call_web_api(some_params)

该模块当前已导入并由多个客户端使用。

从今天开始,对get_data的调用本质上是 synchronous :这意味着使用函数fetcher.read()的人都知道这将阻塞执行该函数的线程。< / p>

我想实现的目标

我想允许fetcher.read()以同步和异步方式(例如通过事件循环)两者运行。 这是为了保持与使用该模块的现有呼叫者的兼容性,同时提供了可能性 利用非阻塞调用为希望异步调用该函数的调用者提供更好的吞吐量。

这就是说,我的合法愿望是尽可能少地修改原始代码...

从今天开始,我唯一了解的是请求不支持开箱即用的异步操作,因此我应该切换到异步友好的HTTP客户端(例如aiohttp)以提供一个无阻塞行为

如何修改上面的代码来满足我的需求?这也使我问:是否有将同步软件API增强到异步上下文的最佳实践? strong>

1 个答案:

答案 0 :(得分:1)

  

我想允许fetcher.read()以同步和异步方式(例如,通过事件循环)运行。

我认为通过同步和异步API使用同一功能是不可行的,因为使用模式非常不同。即使您能以某种方式使其工作,将事情弄乱也太容易了,特别是考虑到Python的动态键入特性。 (例如,用户可能不小心忘记了用异步代码await来执行其功能,并且同步代码会启动,从而阻塞了事件循环。)

相反,我建议实际的API是异步的,并创建一个简单的同步包装器,该包装器仅使用run_until_complete调用入口点。遵循以下原则:

# new module afetcher.py (or fetcher_async, or however you like it)

import aiohttp

# high-level module API
async def read(some_params):
    async with aiohttp.request('GET', 'http://example.com', params=some_params) as resp:
        return await resp.json()

# wrapper for the actual remote API call
async def get_data(some_params):
    return call_web_api(some_params)

是的,您从使用requests切换到aiohttp,但是更改是机械的,因为API的本质非常相似。

存在同步模块是为了向后兼容和方便,并且将包装异步功能:

# module fetcher.py

import afetcher

def read(some_params):
    loop = asyncio.get_event_loop()
    return loop.run_until_complete(afetcher.read(some_params))

...

这种方法同时提供了API的同步版本和异步版本,而无需重复代码,因为同步版本由简单的蹦床组成,可以使用适当的装饰器进一步压缩其定义。

异步提取器模块应该有一个不错的简称,这样用户就不会因为使用异步功能而受到惩罚。它应该易于使用,并且与sync API相比,它实际上提供了许多新功能,其中最值得一提的是开销较低的并行化和可靠的取消。

建议的路由是使用run_in_executor或类似的基于线程的工具在引擎盖下的线程池中运行requests。该实现不提供使用asyncio的实际好处,但会产生所有费用。在这种情况下,最好继续提供同步API,并让用户使用concurrent.futures或类似的工具进行并行执行,以便他们至少知道自己在使用线程。