I have a code:
import asyncio as aio
async def coro(future: aio.Future):
print('Coro start')
await aio.sleep(3)
print('Coro finish')
future.set_result('coro result')
async def main():
future = aio.Future()
aio.create_task(coro(future))
await future
coro_result = future.result()
print(coro_result)
aio.run(main())
In main()
I create an empty aio.Future object, then I create a task with aio.create_task(coro(future))
using coroutine which takes aio.Future object. Then I 'run' the empty future with await future
. Somehow this line runs the task instead of running the empty coroutine! I don't understand how it works and why it goes like this, because I expect the line await future
to run the empty future, not task!
If I reorganize my main()
like this:
import asyncio as aio
async def coro(future: aio.Future):
print('Coro start')
await aio.sleep(3)
print('Coro finish')
future.set_result('coro result')
async def main():
future = aio.Future()
await aio.create_task(coro(future))
# await future
coro_result = future.result()
print(coro_result)
aio.run(main())
I get the same result but the code behaviour becomes much more explicit for me.
First, let's clear up some terminology. You said, "Then I 'run' the empty future with await future ..." A future is not "run". A future represents a value that will be set in the future. If you await
the future, there has to be some other task that calls set_result
on the future before your await
is satisfied.
Then you said, "Somehow this line (await future
) runs the task instead of running the empty coroutine!" I don't know what you mean by an "empty coroutine". Let's see what is actually happening:
In main
you create a task with aio.create_task(coro(future))
. First, you should ideally assign the task instance that was created to some variable so that a reference to the task exists preventing the task from being prematurely garbage collected (and thus terminated). For example,
task = aio.create_task(coro(future))
Now that you have created a task, it will potentially execute (depending on what other tasks exist) as soon as main
either executes an await
statement or returns. Thus the mere fact that you execute await future
is sufficient to cause function coro
to start running. coro
sets a result in the future and when it issues an await
or returns, then another task gets a chance to run. In this case coro
returns and the await
issued on the future by main
completes.
Your second example is less than ideal. main
wants to wait for the future to be set with a value. This setting is being done by coro
so clearly if you wait for coro
to complete you will discover that your future has been set. But what if coro
is a very long running task and sets a value in the future long before it terminates? In this case main
will be waiting an unnecessarily long period of time since the future it is interested in was set long before coro
ever terminated. Your code should therefore be:
import asyncio as aio
async def coro(future: aio.Future):
print('Coro start')
# For demo purposes we set the future right away:
future.set_result('coro result')
await aio.sleep(3)
print('Coro finish')
async def main():
future = aio.Future()
task = aio.create_task(coro(future))
# We are interested in examining the future as soon
# as it gets a result, which may be before coro terminates:
await future
# Now we can call `result` on the future even though coro will
# not terminate for 3 more seconds:
coro_result = future.result()
print(coro_result)
await task # Give coro a chance to finish
aio.run(main())
Prints:
Coro start
coro result
Coro finish