我觉得在代码方面更容易理解:
async for item in timeout(something(), timeout=60, sentinel=None):
if item is not None:
await do_something_useful(item)
await refresh()
我希望协程至少每60秒执行一次refresh
。
答案 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
并不难。
要澄清的第二件事是异步迭代器的本质。据我了解,如果发生超时,您可以使用它来获取something
或None
的结果。
如果我正确理解逻辑,那么使用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()
包装在一个任务中,然后对其进行屏蔽,以便能够恢复迭代器。