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.
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.
Share Improve this question asked Jan 20 at 8:05 IzaeDAIzaeDA 3335 bronze badges 2 |3 Answers
Reset to default 3First, 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
I feel like there is a misconception of what a future is and how it works, so simply a future is an object that has a result which is not available yet and will be set in the future (obvious by the name), and it is mainly used inside of Asnycio through other functions and such but we can use futures outside of it as well.
Its pretty much a placeholder which we can set either a result/value to or an exception, and in the situation where you are creating a task and it "fills" up the future that's the point of it, so that the future is populated.
The following will explain the concept better in a code manner:
async def main():
future = aio.Future()
# Create a task that will set the result of 'future'
aio.create_task(coro(future))
# This will "block" until 'future' gets a result or exception
await future
# Retrieves the result of 'future' once it's set
coro_result = future.result()
print(coro_result)
Hope this helps :)
To keep it simple, aio.create_task(coro(future))
already schedules (launches) the coro
coroutine . Yet main
is not suspended right away, it continues to execute (unless one or another await gives task a chance to execute). await future
suspends the main coroutine and waits until future
gets assigned the final result. It does not start the task per se, the task is already launched, just gives its opportunity to run and ensures completion.
await aio.create_task(coro(future))
in the main co-routine not only creates the task, but awaits until the coro
task is finished.
Either way you need at least one await
to make sure the result is ready
await
as meaning "wait for", like it does in the dictionary, instead of meaning "run". – Dan Getz Commented Jan 20 at 16:27await aio.create_task(coro(future))
waits for corutine to finish,await future
waits for future to get assigned final result. You need to have at the least one await to avoid error – Serge Commented Jan 20 at 19:30