8

我最近一直在玩弄 asyncio,虽然我开始对它的工作原理有了直觉,但有些事情我做不到。我不确定这是否是因为我的构造错误,或者是否有原因使我尝试做的事情没有意义。

简而言之,我希望能够迭代产生的 asyncio.coroutine。例如,我希望能够执行以下操作:

@asyncio.coroutine
def countdown(n):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield n

@asyncio.coroutine
def do_work():
    for n in countdown(5):
        print(n)

loop.run_until_complete(do_work())

但是,这会从 asyncio 的内部引发异常。我已经尝试过其他的东西,比如for n in (yield from countdown(5)): ...但这也给出了类似的不透明运行时异常。

我不能立即明白为什么你不应该做这样的事情,但我已经达到了我理解正在发生的事情的能力的极限。

所以:

  • 如果可以做到这一点,我该怎么做?
  • 如果不可能,为什么不呢?

如果这个问题不清楚,请告诉我!

4

3 回答 3

5

在 asyncio 协程中,您应该使用yield from并且永远不要使用yield. 这是设计使然。for 的参数yield from应该是另一个协程或asyncio.Future实例。

协程本身的调用应该yield from再次与 like一起使用yield from countdown(5)

对于您的情况,我建议使用队列:

import asyncio

@asyncio.coroutine
def countdown(n, queue):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield from queue.put(n)
    yield from queue.put(None)

@asyncio.coroutine
def do_work():
    queue = asyncio.Queue()
    asyncio.async(countdown(5, queue))
    while True:
        v = yield from queue.get()
        if v:
            print(v)
        else:
            break

asyncio.get_event_loop().run_until_complete(do_work())

好吧,您可以使用检查产生的值countdown,以下示例有效。但我认为这是反模式:

  1. 太容易搞砸了

  2. 无论如何,你不能countdownitertools函数来编写调用。我的意思是类似sum(countdown(5))or itertools.accumulate(countdown(5))

无论如何,混合yieldyield from协同程序的示例:

import asyncio

@asyncio.coroutine
def countdown(n):
    while n > 0:
        yield from asyncio.sleep(1)
        n = n - 1
        yield n

@asyncio.coroutine
def do_work():
    for n in countdown(5):
        if isinstance(n, asyncio.Future):
            yield from n
        else:
            print(n)

asyncio.get_event_loop().run_until_complete(do_work())
于 2014-05-17T12:47:53.833 回答
4

在 Python 3.5 中,async for引入了语法。但是,异步迭代器函数语法仍然不存在(即yield在函数中被禁止async)。这是一个解决方法:

import asyncio
import inspect

class escape(object):
    def __init__(self, value):
        self.value = value

class _asynciter(object):
    def __init__(self, iterator):
        self.itr = iterator
    async def __aiter__(self):
        return self
    async def __anext__(self):
        try:
            yielded = next(self.itr)
            while inspect.isawaitable(yielded):
                try:
                    result = await yielded
                except Exception as e:
                    yielded = self.itr.throw(e)
                else:
                    yielded = self.itr.send(result)
            else:
                if isinstance(yielded, escape):
                    return yielded.value
                else:
                    return yielded
        except StopIteration:
            raise StopAsyncIteration

def asynciter(f):
    return lambda *arg, **kwarg: _asynciter(f(*arg, **kwarg))

那么你的代码可以写成:

@asynciter
def countdown(n):
    while n > 0:
        yield from asyncio.sleep(1)
        #or:
        #yield asyncio.sleep(1)
        n = n - 1
        yield n

async def do_work():
    async for n in countdown(5):
        print(n)

asyncio.get_event_loop().run_until_complete(do_work())

要了解新语法以及此代码的工作原理,请参阅PEP 492

于 2015-09-09T13:14:46.010 回答
1

更新:似乎 python 3.5在本机上更好地支持这一点

遇到同样的问题(并受到aio-s3中代码的启发),我觉得应该有一个更优雅的解决方案。

import asyncio

def countdown(number):
    @asyncio.coroutine
    def sleep(returnvalue):
        yield from asyncio.sleep(1)
        return returnvalue
    for n in range(number, 0, -1):
        yield sleep(n)

@asyncio.coroutine
def print_countdown():
    for future in countdown(5):
        n = yield from future
        print ("Counting down: %d" % n)

asyncio.get_event_loop().run_until_complete(print_countdown())

基本原理:该countdown方法产生期货,每个期货将在 1 秒睡眠后解析为提供的数字。

print_countdown函数采用第一个未来,yield from-ing 它(将暂停直到它解决)并获得预期的结果:n.

于 2015-09-02T14:28:53.727 回答