如何缓存asyncio协程

我正在使用aiohttp在Python 3.4中创建一个简单的HTTP请求,如下所示:

response = yield from aiohttp.get(url)

应用程序一次又一次地请求相同的URL,所以很自然地我想缓存它。 我的第一次尝试是这样的:

@functools.lru_cache(maxsize=128)
def cached_request(url):
    return aiohttp.get(url)

第一次调用cached_request可以正常工作,但在以后的调用中,我最终得到了None而不是响应对象。

我对asyncio比较陌生,所以我尝试了很多asyncio.coroutine装饰器, yield from和其他一些东西的组合,但没有一个可以工作。

那么缓存协程是如何工作的呢?


我自己写了一个简单的缓存修饰器:

def async_cache(maxsize=128):
    cache = {}

    def decorator(fn):
        def wrapper(*args):                                                         
            key = ':'.join(args)

            if key not in cache:
                if len(cache) >= maxsize:
                    del cache[cache.keys().next()]

                cache[key] = yield from fn(*args)

            return cache[key]

        return wrapper

    return decorator


@async_cache()
@asyncio.coroutine
def expensive_io():
    ....

这种工作。 但许多方面可能可以改进。 例如:如果在第一次调用返回之前第二次调用缓存的函数,它将再次执行。


也许有点晚,但我已经开始了一个可能有所帮助的新软件包:https://github.com/argaen/aiocache。 贡献/意见总是受欢迎的。

一个例子:

import asyncio

from collections import namedtuple

from aiocache import cached
from aiocache.serializers import PickleSerializer

Result = namedtuple('Result', "content, status")


@cached(ttl=10, serializer=PickleSerializer())
async def async_main():
    print("First ASYNC non cached call...")
    await asyncio.sleep(1)
    return Result("content", 200)


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    print(loop.run_until_complete(async_main()))
    print(loop.run_until_complete(async_main()))
    print(loop.run_until_complete(async_main()))
    print(loop.run_until_complete(async_main()))

请注意,作为额外的,它可以使用Pickle序列化将任何python对象缓存到redis中。 如果你只是想处理内存,你可以使用SimpleMemoryCache后端:)。


要与协程一起使用functools.lru_cache ,以下代码有效。

class Cacheable:
    def __init__(self, co):
        self.co = co
        self.done = False
        self.result = None
        self.lock = asyncio.Lock()

    def __await__(self):
        with (yield from self.lock):
            if self.done:
                return self.result
            self.result = yield from self.co.__await__()
            self.done = True
            return self.result

def cacheable(f):
    def wrapped(*args, **kwargs):
        r = f(*args, **kwargs)
        return Cacheable(r)
    return wrapped


@functools.lru_cache()
@cacheable
async def foo():
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

以下是线程安全的

class ThreadSafeCacheable:
    def __init__(self, co):
        self.co = co
        self.done = False
        self.result = None
        self.lock = threading.Lock()

    def __await__(self):
        while True:
            if self.done:
                return self.result
            if self.lock.acquire(blocking=False):
                self.result = yield from self.co.__await__()
                self.done = True
                return self.result
            else:
                yield from asyncio.sleep(0.005)
链接地址: http://www.djcxy.com/p/53223.html

上一篇: how to cache asyncio coroutines

下一篇: Calling a coroutine from asyncio.Protocol.data