2

Complete newb here, reading about Asycnio Tasks which has this example:

import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

My original understanding of await is that it blocks the execution of current function and wait for the async function to return.

But in this case both coroutines are executed concurrently, it doesn't fit well my understanding of await. Could someone please explain?

Further investigation, by adding extra print in say_after, it seems to me the coroutine doesn't start until await happens...

import asyncio
import time

async def say_after(delay, what):
    print('Received {}'.format(what))
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

prints

started at 13:41:23
Received hello
Received world
hello
world
finished at 13:41:25
James Lin
  • 25,028
  • 36
  • 133
  • 233

3 Answers3

2

Your understanding of await is correct. It does pause the execution of the main function.

The key is that asyncio.create_task() creates a task and schedules it.

So the say_after function starts running here:

task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

and not when you await.

See here: https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task

tsuyoku
  • 46
  • 4
  • well yeah, I know it's the task, but it seems weird that `await` changes its behaviour if it's a task? – James Lin May 22 '19 at 21:29
  • Could you clarify how you think it's behavior changes? `await` makes main wait until the async function `say_after` returns. – tsuyoku May 22 '19 at 21:33
  • the coroutine doesn't start until `await task1`, so it's not like `thread.join` – James Lin May 23 '19 at 01:35
  • It starts at `asyncio.create_task(say_after(1, 'hello'))`, when the task is created. try running the code without the `await` lines you will find that the program prints out `hello world` still. – tsuyoku May 23 '19 at 01:56
  • Have a look at my latest update, it prints `Received word` after printing `Started timestamp` – James Lin May 23 '19 at 01:57
  • The `say_after` function doesn't start running at `create_task`, it starts running at the next entry into the event loop. This is what happens (usually) at the next `await`, regardless of what is being awaited. – user4815162342 May 25 '19 at 05:47
1

When you encapsulate a coroutine in a Task (or Future) object the coroutine is ready to work, so when the event loop start running on the first await, both task1 and task2 are running.

Trying to make it clearer, to execute a coroutine you need two things:
1) a coroutine incapsulated in a future object(Task) to make it awaitable
2) a running event loop

In your example the execution works like this:
1 - create_task1
2 - create_task2
3 - await task1
4 - await sleep of task1
5 - await sleep of task2

now both task1 and task2 are sleeping so, suppose task1 is the first to finish (sleep some time)

6 - print of task1
7 - await task2
8 - print of task2

now the loop end

As you said when you got an await the execution stops, but let me say that stops just in the current "execution flow", when you create a future(Task) you create another exucution flow, and the await just switch to the current execution flow. This last explanation is not completely correct in sens of terms but help to make it clearer.

I hope i was clear. P.S.: sorry for my bad english.

Fanto
  • 128
  • 1
  • 9
  • `create_task()` does not start the coroutine immediately, it only starts until `await task1`, but my understanding is that `awake task1` should block executing `await task2` until `task1` is finished. – James Lin May 23 '19 at 01:33
  • I think that your understanding is correct but just in the view of the function that call the await, in a global point of view the await just switch the execution to another task, that's why the multitasking with coroutine is cooperative and not preemptive. – Fanto May 23 '19 at 07:25
  • I just cannot get pass that fact that `await task1` blocks start executing `await task2`, if `await task2` is not executed in the current function context then how can `task2` run concurrently with `task1`. – James Lin May 27 '19 at 20:44
1

OK Both @tsuyoku and @Fanto answers are correct, this answer is just to compliment the existing answers, the important bit for me that I could not grasp was the execution started on create_task():

import asyncio
import time

async def say_after(delay, what):
    print('Received {}'.format(what))
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello')
    )

    task2 = asyncio.create_task(
        say_after(2, 'world')
    )

    print(f"started at {time.strftime('%X')}")
    await asyncio.sleep(10)

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task2
    print('task 2 finished')
    await task1
    print('task 1 finished')

    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

prints

started at 10:42:10
Received hello
Received world
hello
world
task 2 finished
task 1 finished
finished at 10:42:20

the original misunderstanding is that stuff takes a bit of time to run the tasks, and the original print out in my question misguided me to think the task doesn't run until the await statement.

James Lin
  • 25,028
  • 36
  • 133
  • 233