5

I'm new to Python asyncio and I'm doing some experiments. I have the following code:

async def say_after(n, s):
    await asyncio.sleep(n)
    print(s)

async def main():
    task1 = asyncio.create_task(say_after(2, 'a'))
    task2 = asyncio.create_task(say_after(1, 'b'))
    await task1
    print('x', flush=True)
    await task2
    print('y', flush=True)

asyncio.run(main())

And the output:

b
a
x
y

I don't understand the order here. Can someone help explain? Especially why x comes after b and a?

  • 1
    If you try the code it actually only prints `x` and `y`. If you await the tasks, the output is the following: `x y b a` – Łukasz Kwieciński Jan 22 '22 at 15:41
  • @ŁukaszKwieciński Hi sorry I copied a wrong version of the code... I have updated the code and it should produce the same output as in the question. – Weiss Zucker Jan 22 '22 at 17:40
  • 3
    Already burned my closure vote prior to the edit (thanks, much clearer now!), but I think [What does asyncio.create_task() do?](https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do) is the proper answer. You're spawning new tasks; simply omit this call if you want to keep everything sequential in a single task. – ggorlen Jan 22 '22 at 18:10
  • @ggorlen thanks! This one really helped me to understand what is a task. – Weiss Zucker Jan 22 '22 at 18:42

3 Answers3

1

Executing say_after (without await) creates a coroutine object, but does not start it yet.

If you await on the coroutine object, then you are executing the coroutine until the Python encounters one of await or return (or end of function) in the coroutine. "Executing" here means transforming the coroutine into a Task object and put that object in the async loop.

However, create_task immediately 'starts' the coroutine and put them tasks in the async loop (though, because this is async instead of parallel, execution does not actually begin until Python encounters await in main()).

In your situation, as soon as Python sees the await task1, it 'leaves' main() and loops among task1 and task2 back and forth (because both tasks have been put in the async loop by create_task) until task1 is completed (because task1 is the one being await-ed on). Because task2 had scheduled itself to wait for a shorter time, it finishes first. About 1 second later, task1 completes, and only then execution returns to main() (because, remember, it was task1 that main() had been await-ing on).

At this point, both task1 and task2 has completed; the await task2 line practically does nothing; it's just "wrapping up" task2 and (almost) immediately returns.

pepoluan
  • 6,132
  • 4
  • 46
  • 76
0

Especially why x comes after b and a?

b is printed in task2 about 1 second after the start of main, and a is printed in task1 about 2 seconds after the start of main. The await task1 waits for task1, and so waits about 2 seconds. So by the time await task1 completes, both b and a would have been printed.

(The "about"s in the above are deliberate... there would be variations, but in most situations, they would be small)

Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
0

await task1 basically says that do not execute the next lines of code until task1 is finished (i.e. the execution of say_after(2, 'a')).

task1 takes longer than task2 to execute, so by the time you "awaited" task1, task2 is already finished executing. So putting await task2 below await task1 is somewhat useless here. If you swap these two lines, the output will be different.

That's why 'b' is printed before 'a'. And not until 'a' is printed, 'x' and 'y' could be printed.