在协同链接期间,await如何将控制权交还给事件循环?

时间:2018-03-12 02:12:14

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

我在Python 3.6中尝试使用asyncio,并且很难弄清楚为什么这段代码的行为方式如此。

示例代码:

import asyncio

async def compute_sum(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(5)
    print("Returning sum")
    return x + y

async def compute_product(x, y):
    print("Compute %s x %s ..." % (x, y))
    print("Returning product")
    return x * y

async def print_computation(x, y):
    result_sum = await compute_sum(x, y)
    result_product = await compute_product(x, y)
    print("%s + %s = %s" % (x, y, result_sum))
    print("%s * %s = %s" % (x, y, result_product))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_computation(1, 2))

输出:

Compute 1 + 2 ...
Returning sum
Compute 1 x 2 ...
Returning product
1 + 2 = 3
1 * 2 = 2

预期输出:

Compute 1 + 2 ...
Compute 1 x 2 ...
Returning product
Returning sum
1 + 2 = 3
1 * 2 = 2

我对预期输出的推理:

虽然在compute_product协程之前正确调用了compute_sum协程,但我的理解是,一旦我们点击await asyncio.sleep(5),控件将被传递回事件循环,这将开始执行compute_product协程。为什么"返还总和"在我们点击compute_product协程中的print语句之前执行?

3 个答案:

答案 0 :(得分:7)

关于协程如何工作你是对的;你的问题在于如何调用他们。特别是:

result_sum = await compute_sum(x, y)

这会调用协程compute_sum ,然后等待它完成

因此,compute_sum确实屈服于await asyncio.sleep(5)中的调度程序,但没有其他人可以唤醒。您的print_computation coro已在等待compute_sum。而且还没有人开始compute_product,所以它肯定无法运行。

如果你想要启动多个协同程序并同时运行它们,请不要await每个协同程序;你需要一起等待他们中的很多人。例如:

async def print_computation(x, y):
    awaitable_sum = compute_sum(x, y)
    awaitable_product = compute_product(x, y)        
    result_sum, result_product = await asyncio.gather(awaitable_sum, awaitable_product)
    print("%s + %s = %s" % (x, y, result_sum))
    print("%s * %s = %s" % (x, y, result_product))

awaitable_sum是一个简单的协程,一个Future对象,还是其他可以await编辑的内容并不重要; gather无论哪种方式都可以。 )

或者,或许更简单:

async def print_computation(x, y):
    result_sum, result_product = await asyncio.gather(
        compute_sum(x, y), compute_product(x, y))
    print("%s + %s = %s" % (x, y, result_sum))
    print("%s * %s = %s" % (x, y, result_product))

请参阅示例部分中的Parallel execution of tasks

答案 1 :(得分:1)

accepted answer上进行扩展,asyncio.gather()在幕后的作用是将每个协程包裹在Task中,这表示在后台完成工作。

您可以将Task对象视为Future对象,它们表示不同线程中可调用对象的执行,只是协程不是对线程的抽象。

通过ThreadPoolExecutor.submit(fn)创建Future实例的方式相同,可以使用asyncio.ensure_feature(coro())创建Task

通过等待任务而不是协程,您的示例可以按预期工作:

async def print_computation(x, y): 
    task_sum = asyncio.ensure_future(compute_sum(x, y)) 
    task_product = asyncio.ensure_future(compute_product(x, y)) 
    result_sum = await task_sum 
    result_product = await task_product 
    print("%s + %s = %s" % (x, y, result_sum)) 
    print("%s * %s = %s" % (x, y, result_product))

输出:

Compute 1 + 2 ...
Compute 1 x 2 ...
Returning product
Returning sum
1 + 2 = 3
1 * 2 = 2

答案 2 :(得分:-1)

以下是它的工作原理。 让我们使用主线程作为主要参考...

主线程处理来自不同位置的事件和工作。如果从其他线程一次触发3个事件,则主线程一次只能处理一个事件。如果主线程正在处理你的循环,它将继续处理它,直到返回方法(或函数),然后再处理其他工作。

这意味着'其他工作'被放置在队列中以在主线程上运行。

使用' async await'你写了'async'让人们知道该方法将(或可以)分解为它自己的一组队列。然后,当你说'等待'它应该在另一个线程上工作。当它发生时,主线程被允许处理存储在队列中的其他事件和工作,而不是只在那里等待。

因此,当await工作完成时,它也将方法的剩余部分放在主线程的队列中。

因此,在这些方法中,它不会继续处理,但会将剩余的工作放在队列中,以便在等待完成时完成。因此,它是有序的。 await compute_sum(x, y)将控制权交还给主线程以进行其他工作,当它完成时,其余部分将被添加到要工作的队列中。所以await compute_product(x, y)在前者完成后排队。