Monday, September 25, 2023

Python Async Execution Order

import asyncio
async def print_(delay, x):
print(f'start: {x}')
await asyncio.sleep(delay)
print(f'end: {x}')

async def main():
slow_task = asyncio.create_task(print_(2, 'slow task'))
fast_task = asyncio.create_task(print_(1, 'fast task'))

print(0)
await slow_task
print(1)
await fast_task
print(2)

asyncio.run(main())
Output:
0
start: slow task
start: fast task
end: fast task
end: slow task
1
2
------ 

import asyncio
async def print_(delay, x):
    print(f'start: {x}')
    await asyncio.sleep(delay)
    print(f'end: {x}')

async def main():
    slow_task = asyncio.create_task(print_(2, 'slow task'))
    fast_task = asyncio.create_task(print_(1, 'fast task'))

    print(0)
    await fast_task
    print(1)
    await slow_task
    print(2)
   
asyncio.run(main())

Output:

0
start: slow task
start: fast task
end: fast task
1
end: slow task
2
 ----------
import asyncio
async def print_(delay, x):
print(f'start: {x}')
await asyncio.sleep(delay)
print(f'end: {x}')

async def main():
fast_task = asyncio.create_task(print_(1, 'fast task'))
slow_task = asyncio.create_task(print_(2, 'slow task'))

print(0)
await slow_task
print(1)
await fast_task
print(2)

asyncio.run(main())
Output:
0
start: fast task
start: slow task
end: fast task
end: slow task
1
2 
import asyncio
async def print_(delay, x):
print(f'start: {x}')
await asyncio.sleep(delay)
print(f'end: {x}')

async def main():
fast_task = asyncio.create_task(print_(1, 'fast task'))
slow_task = asyncio.create_task(print_(2, 'slow task'))

print(0)
await fast_task
print(1)
await slow_task
print(2)

asyncio.run(main())
Output:
0
start: fast task
start: slow task
end: fast task
1
end: slow task
2

 ******

from asyncio import create_task, sleep, run

async def print_(delay, x):
    print(f'start: {x}')
    await sleep(delay)
    print(f'end: {x}')

async def main():
    slow_task = create_task(print_(2, 'slow task'))
    fast_task = create_task(print_(1, 'fast task'))

    # The order of execution here is strange:
    print(0)
    await slow_task
    print(1)
    await fast_task

run(main())
  • You create asynchronous processes for a fast task and a slow task at the same time; even the "fast" one will take a significant amount of time.
  • Immediately after creating them, you print 0, so this becomes your first output.
  • You call await slow_task, passing control to the event loop until slow_task finishes.
    • Because you requested slow_task, it's prioritized, so it starts first, so start: slow_task is printed.
    • Because slow_task contains an await sleep(2), it passes control back to the event loop, which finds fast_task as ready to operate and starts it, so start: fast_task is printed.
    • Because fast_task's await sleep(1) finishes first, fast_task completes, and end: fast_task is printed. Because we're awaiting slow_task, not fast_task, we remain in the event loop.
    • Finally, the slow task finishes, so it prints end: slow task. Because this is what we were awaiting for, control flow is returned to the synchronous process.
  • After the slow task has finished, you print 1, so this becomes your last output.
  • Finally, you wait for the fast task to finish; it already did finish earlier, while you were waiting for the fast task, so this returns immediately.