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

How does await give back control to the event loop during coroutine chaining?

我正在尝试使用 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_sum 协程在 compute_product 协程之前被正确调用,但我的理解是一旦我们点击 await asyncio.sleep(5),控制将被传回事件循环,这将开始执行 compute_product 协程。为什么 "Returning sum" 在我们命中 compute_product 协程中的 print 语句之前被执行?

关于协程的工作原理,你是对的;您的问题在于您如何 呼叫 他们。特别是:

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 对象还是其他可以被 awaited 的对象都无关紧要;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

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

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

这意味着 'other work' 被放置在队列中,以便在主线程上成为 运行。

当您使用 'async await' 时,您会写 'async' 以告知该方法将(或可以)分解到它自己的一组队列中。然后当你说 'await' 它应该在另一个线程上工作。当它执行时,允许主线程处理存储在队列中的其他事件和工作,而不是仅仅在那里等待。

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

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

, what asyncio.gather() does behind the scenes is that it wraps each coroutine in a Task 上展开,它表示正在后台完成的工作。

你可以把Task对象当成Future对象,代表一个可调用对象在不同线程中的执行,只是协程是不是对线程的抽象。

并且 ThreadPoolExecutor.submit(fn), a Task can be created using asyncio.ensure_future(coro()) 以同样的方式创建了 Future 个实例。

通过在等待之前将所有协程安排为任务,您的示例按预期工作:

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