如何使用超时迭代异步迭代器?

时间:2018-05-08 20:19:54

标签: python-3.x python-asyncio

我觉得在代码方面更容易理解:

async for item in timeout(something(), timeout=60, sentinel=None):
    if item is not None:
        await do_something_useful(item)
    await refresh()

我希望协程至少每60秒执行一次refresh

6 个答案:

答案 0 :(得分:2)

我需要做这样的事情来创建一个websocket(也是一个异步迭代器),如果在一定的持续时间内没有收到消息,它会超时。我决定满足以下条件:

socket_iter = socket.__aiter__()
try:
    while True:
        message = await asyncio.wait_for(
            socket_iter.__anext__(),
            timeout=10
        )
except asyncio.futures.TimeoutError:
    # streaming is completed
    pass

答案 1 :(得分:1)

一种简单的方法是使用asyncio.Queue,并将代码分成两个协程:

queue = asyncio.Queue()
async for item in something():
    await queue.put(item)

在另一个协程中:

while True:
    try:
        item = await asyncio.wait_for(queue.get(), 60)
    except asyncio.TimeoutError:
        pass
    else:
        if item is None:
            break  # use None or whatever suits you to gracefully exit
        await do_something_useful(item)
    refresh()

请注意,如果处理程序do_something_useful()something()生成项目慢,它将使队列增长。您可以在队列上设置maxsize以限制缓冲区大小。

答案 2 :(得分:1)

AsyncTimedIterable可能是您代码中timeout()的实现:

class _AsyncTimedIterator:

    __slots__ = ('_iterator', '_timeout', '_sentinel')

    def __init__(self, iterable, timeout, sentinel):
        self._iterator = iterable.__aiter__()
        self._timeout = timeout
        self._sentinel = sentinel

    async def __anext__(self):
        try:
            return await asyncio.wait_for(self._iterator.__anext__(), self._timeout)
        except asyncio.TimeoutError:
            return self._sentinel


class AsyncTimedIterable:

    __slots__ = ('_factory', )

    def __init__(self, iterable, timeout=None, sentinel=None):
        self._factory = lambda: _AsyncTimedIterator(iterable, timeout, sentinel)

    def __aiter__(self):
        return self._factory()

(原始答案)

或者使用此课程替换timeout()函数:

class AsyncTimedIterable:
    def __init__(self, iterable, timeout=None, sentinel=None):
        class AsyncTimedIterator:
            def __init__(self):
                self._iterator = iterable.__aiter__()

            async def __anext__(self):
                try:
                    return await asyncio.wait_for(self._iterator.__anext__(),
                                                  timeout)
                except asyncio.TimeoutError:
                    return sentinel

        self._factory = AsyncTimedIterator

    def __aiter__(self):
        return self._factory()

答案 3 :(得分:1)

根据refresh函数的性质,您的问题的答案可能会有所不同。如果它是非常短的运行功能,它可以在协程内自由调用。但如果它是阻塞功能(由于网络或CPU),它应该运行in executor以避免冻结asyncio事件循环。

下面的代码显示了第一种情况的示例,将其更改为在执行程序中运行refresh并不难。

要澄清的第二件事是异步迭代器的本质。据我了解,如果发生超时,您可以使用它来获取somethingNone的结果。

如果我正确理解逻辑,那么使用async_timeout上下文管理器并且根本不使用异步迭代器,可以更清楚地编写代码(类似于创建asyncio的非异步样式): / p>

import asyncio
from async_timeout import timeout


async def main():
    while True:
        try:
            async with timeout(60):
                res = await something()
                await do_something_useful(item)
        except asyncio.TimeoutError:
            pass
        finally:
            refresh()

答案 4 :(得分:1)

  

我希望协程至少每60秒执行一次refresh

如果您需要每60秒执行refresh,无论do_something_useful发生什么情况,您都可以使用单独的协程进行安排:

import time

async def my_loop():
    # ensure refresh() is invoked at least once in 60 seconds
    done = False
    async def repeat_refresh():
        last_run = time.time()
        while not done:
            await refresh()
            now = time.time()
            await asyncio.sleep(max(60 - (now - last_run), 0))
            last_run = now
    # start repeat_refresh "in the background"
    refresh_task = asyncio.get_event_loop().create_task(repeat_refresh())

    try:
        async for item in something():
            if item is not None:
                await do_something_useful(item)
            await refresh()
    finally:
        done = True

答案 5 :(得分:1)

您的问题缺少几个详细信息,但是假设something()是异步迭代器或生成器,并且您希望item每次sentinel都不会产生{超时内的值,这是something的实现:

timeout()

测试:

import asyncio
from typing import *

T = TypeVar('T')

# async generator, needs python 3.6
async def timeout(it: AsyncIterator[T], timeo: float, sentinel: T) -> AsyncGenerator[T, None]:
    try:
        nxt = asyncio.ensure_future(it.__anext__())
        while True:
            try:
                yield await asyncio.wait_for(asyncio.shield(nxt), timeo)
                nxt = asyncio.ensure_future(it.__anext__())
            except asyncio.TimeoutError:
                yield sentinel
    except StopAsyncIteration:
        pass
    finally:
        nxt.cancel()  # in case we're getting cancelled our self

async def something(): yield 1 await asyncio.sleep(1.1) yield 2 await asyncio.sleep(2.1) yield 3 async def test(): expect = [1, None, 2, None, None, 3] async for item in timeout(something(), 1, None): print("Check", item) assert item == expect.pop(0) asyncio.get_event_loop().run_until_complete(test()) 超时时,它将取消任务。因此,我们需要将wait_for()包装在一个任务中,然后对其进行屏蔽,以便能够恢复迭代器。