I'm trying my hand at asyncio in Python 3.6 and having a hard time figuring out why this piece of code is behaving the way it is.
Example code:
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))
Output:
Compute 1 + 2 ...
Returning sum
Compute 1 x 2 ...
Returning product
1 + 2 = 3
1 * 2 = 2
Expected Output:
Compute 1 + 2 ...
Compute 1 x 2 ...
Returning product
Returning sum
1 + 2 = 3
1 * 2 = 2
My reasoning for expected output:
While the compute_sum coroutine is correctly called before the compute_product coroutine, my understanding was that once we hit await asyncio.sleep(5)
, the control would be passed back to the event loop which would start the execution of the compute_product coroutine. Why is "Returning sum" being executed before we hit the print statement in the compute_product coroutine?
The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run subprocesses. Application developers should typically use the high-level asyncio functions, such as asyncio.
In Python, we need an await keyword before each coroutine object to have it called by the event loop. But when we put await , it makes the call blocking. It follows that we end up doing the same thing as we do in the blocking fashion.
It'll block event loop at a point.
Correct OP answer: No, await (per se) does not yield to the event loop, yield yields to the event loop, hence for the case given: "(2) We jump directly into send_message ".
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With