Comment by amluto
I can't quite parse what you're saying.
Python works like this:
import asyncio
async def sleepy() -> None:
print('Sleepy started')
await asyncio.sleep(0.25)
print('Sleepy resumed once')
await asyncio.sleep(0.25)
print('Sleepy resumed and is done!')
async def main():
sleepy_future = sleepy()
print('Started a sleepy')
await asyncio.sleep(2)
print('Main woke back up. Time to await the sleepy.')
await sleepy_future
if __name__ == "__main__":
asyncio.run(main())
Running it does this: $ python3 ./silly_async.py
Started a sleepy
Main woke back up. Time to await the sleepy.
Sleepy started
Sleepy resumed once
Sleepy resumed and is done!
So there mere act of creating a coroutine does not cause the runtime to run it. But if you explicitly create a task, it does get run: import asyncio
async def sleepy() -> None:
print('Sleepy started')
await asyncio.sleep(0.25)
print('Sleepy resumed once')
await asyncio.sleep(0.25)
print('Sleepy resumed and is done!')
async def main():
sleepy_future = sleepy()
print('Started a sleepy')
sleepy_task = asyncio.create_task(sleepy_future)
print('The sleepy future is now in a task')
await asyncio.sleep(2)
print('Main woke back up. Time to await the task.')
await sleepy_task
if __name__ == "__main__":
asyncio.run(main())
$ python3 ./silly_async.py
Started a sleepy
The sleepy future is now in a task
Sleepy started
Sleepy resumed once
Sleepy resumed and is done!
Main woke back up. Time to await the task.
I personally like the behavior of coroutines not running unless you tell them to run -- it makes it easier to reason about what code runs when. But I do not particularly like the way that Python obscures the difference between a future-like thing that is a coroutine and a future-like thing that is a task.
That’s exactly the behavior I’m describing.
`sleepy_future = sleepy()` creates the state machine without running anything, `create_task` actually schedules it to run via a queue, `asyncio.sleep` suspends the main task so that the newly scheduled task can run, and `await sleepy_task` either yields the main task until sleepy_task can finish, or no-ops immediately if it has already finished without yielding the main task.
My original point is that last bit is a very common optimization in languages with async/await since if the future has already resolved, there’s no reason to suspend the current task and pay the switching overhead if the task isn’t blocked waiting for anything.