async和await关键字如何正常工作?什么是await链的末尾?

时间:2017-03-21 02:00:05

标签: python python-3.x async-await python-asyncio

我有这段代码:

async def foo(x):
    yield x
    yield x + 1

async def intermediary(y):
    await foo(y)

def bar():
    c = intermediary(5)

我在bar中添加了什么来获取c中的5和6?

我问,因为asyncio库似乎很多魔法。而且我想知道魔法是如何运作的。

也许我想编写自己的函数来调用readwrite,然后通知我写的一些顶级循环,他们正在等待文件描述符变得可读或可写。 / p>

然后,也许我希望这个顶级循环能够在这些条件成立后恢复我的读写函数(以及顶级循环和它们之间的整个中间链)。

我已经知道如何使用 asyncio或多或少。我写了这个little demo program来计算延迟后的方格,但是会在随机间隔之后启动大量的每个附加到列表的任务。它有点笨拙,但它确实有效。

我想确切地知道该计划正在做什么。为了做到这一点,我必须知道如何等待睡眠通知顶级事件循环它想要睡眠(并再次被调用)一点以及调用之间所有中间堆栈帧的状态如何睡眠和顶级事件循环被冻结到位,然后在延迟结束时重新激活。

4 个答案:

答案 0 :(得分:3)

您是否尝试过查看asyncio.sleep的来源?

@coroutine                                                                       
def sleep(delay, result=None, *, loop=None):                                     
    """Coroutine that completes after a given time (in seconds)."""              
    if delay == 0:                                                               
        yield                                                                    
        return result                                                            

    if loop is None:                                                             
        loop = events.get_event_loop()                                           
    future = loop.create_future()                                                
    h = future._loop.call_later(delay,                                           
                                futures._set_result_unless_cancelled,            
                                future, result)                                  
    try:                                                                         
        return (yield from future)                                               
    finally:                                                                     
        h.cancel()

基本上它使用loop.call_later来设置未来,然后等待未来。不确定这完全回答了你的问题,但它可能有所帮助。

答案 1 :(得分:2)

所以,我更了解如何制作我想做的工作。这就是我的代码应该如何阅读:

import types

@types.coroutine
def foo(x):
    yield x
    yield x + 1

async def intermediary(y):
    await foo(y)

def bar():
    c = intermediary(5)
    try:
        while True:
            result = c.send(None)
            print(f"Got {result} from the coroutine.")
    except StopIteration as e:
        print(f"StopIteration exception: {e!r}")

基本答案是,此端点可以是用types.coroutine修饰的普通生成器。有更多的方法可以完成这项工作,我的代码的这种进一步修改演示了它们:

import types
from collections.abc import Awaitable

@types.coroutine
def foo(x):
    sent = yield x
    print(f"foo was sent {sent!r}.")
    sent = yield x + 1
    print(f"foo was sent {sent!r}.")
    return 'generator'

class MyAwaitable(Awaitable):
    def __init__(self, x):
        super().__init__()
        self.x_ = x
    def __await__(self):
        def gen(x):
            for i in range(x-1, x+2):
                sent = yield i
                print(f"MyAwaitable was sent {sent!r}.")
            return 'class'
        return iter(gen(self.x_))

async def intermediary(t, y):
    awaited = await t(y)
    print(f"Got {awaited!r} as value from await.")

def runco(chain_end):
    c = intermediary(chain_end, 5)
    try:
        sendval = None
        while True:
            result = c.send(sendval)
            print(f"Got {result} from the coroutine.")
            sendval = sendval + 1 if sendval is not None else 0
    except StopIteration as e:
        print(f"StopIteration exception: {e!r}")

正如您所看到的,定义返回迭代器的__await__方法的任何内容也可以await。真正发生的是被await编辑的东西被迭代直到它停止然后await返回。你这样做的原因是链末端的最后一件事可能遇到某种阻塞条件。然后,它可以通过yield或从迭代器返回一个值来报告该条件(或者要求设置回调或其他内容)(基本上与yield ing相同)。然后顶级循环可以继续运行其他任何东西。

await调用的整个链的本质是当你然后返回并从迭代器中请求下一个值时(回调到被阻塞的函数,告诉它可能现在没有被阻塞)重新激活整个调用堆栈。整个链作为一种在呼叫被阻止时保留调用堆栈状态的方式而存在。基本上是一个自愿放弃控制而不是由调度程序从控制中获取控制权的线程。

当我问这个问题时,我内心的asyncio内部工作的愿景显然是curio的某些东西是如何工作的,并且是基于端点例程yield某种指示它们被阻止的内容以及运行它的顶级循环(在我的示例中为runco),然后将其放入某种常规条件池中以便查找,以便它可以尽快恢复例程作为条件,它被变化阻止了。在asyncio中,会发生更复杂的事情,并且它使用带有__await__方法的对象(例如我的示例中的MyAwaitable)和某种回调机制使其全部工作。

Brett Cannon撰写了一篇非常好的文章,讨论how generators evolved into coroutines。它将详细介绍我在StackOverflow答案中可以进行的更多细节。

我发现的一个有趣的小问题是当你这样做时:

def foo(x):
    yield 11

bar = types.coroutine(foo)

foobar都成为'coroutines',可以await。所有装饰器都在foo.__code__.co_flags中翻转了一下。当然,这是一个实施细节,不应该依赖。我认为这实际上是一个bug,我可能会这样报告。

答案 2 :(得分:0)

有一个example in the documentation,看起来几乎就像你想要做的那样。它包含一个睡眠调用(用来代替IO),因此asyncio方面是有意义的。

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

答案 3 :(得分:0)

通过上面提供的代码,包含async def的{​​{1}}会创建Asynchronous Generator

yield

要使用其中的数据,请使用async def foo(x): yield x yield x + 1

async for

要从常规函数中使用来自async def intermediary(y): results = [] async for x in foo(y): results.append(x) return results 之类的简单协程的结果,您需要创建一个事件循环并使用intermediary

run_until_complete()