How does await give back control to the event loop during coroutine chaining?
You're right about how the coroutines work; your problem is in how you're calling them. In particular:
result_sum = await compute_sum(x, y)
This calls the coroutine compute_sum
and then waits until it finishes.
So, compute_sum
does indeed yield to the scheduler in that await asyncio.sleep(5)
, but there's nobody else to wake up. Your print_computation
coro is already awaiting compute_sum
. And nobody's even started compute_product
yet, so it certainly can't run.
If you want to spin up multiple coroutines and have them run concurrently, don't await
each one; you need to await the whole lot of them together. For example:
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))
(It doesn't matter whether awaitable_sum
is a bare coroutine, a Future
object, or something else that can be await
ed; gather
works either way.)
Or, maybe more simply:
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))
See Parallel execution of tasks in the examples section.
Expanding on the accepted answer, what asyncio.gather()
does behind the scenes is that it wraps each coroutine in a Task
, which represents work being done in the background.
You can think of Task
objects as Future
objects, which represent the execution of a callable in a different Thread, except that coroutines are not an abstraction over threading.
And in the same way Future
instances are created by ThreadPoolExecutor.submit(fn)
, a Task
can be created using asyncio.ensure_future(coro())
.
By scheduling all coroutines as tasks before awaiting them, your example works as expected:
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))
Output:
Compute 1 + 2 ...
Compute 1 x 2 ...
Returning product
Returning sum
1 + 2 = 3
1 * 2 = 2