A conceptual overview of !asyncio — Awaiting coroutines

Unlike tasks, awaiting a coroutine does not hand control back to the event loop! Wrapping a coroutine in a task first, then awaiting that would cede control. The behavior of await coroutine is effectively the same as invoking a regular, synchronous Python function. Consider this program

async def coro_a(): print("I am coro_a(). Hi!")

async def coro_b(): print("I am coro_b(). I sure hope no one hogs the event loop...")

async def main(): task_b = asyncio.create_task(coro_b()) num_repeats = 3 for _ in range(num_repeats): await coro_a() await task_b

The first statement in the coroutine main() creates task_b and schedules it for execution via the event loop. Then, coro_a() is repeatedly awaited. Control never cedes to the event loop, which is why we see the output of all three coro_a() invocations before coro_b()'s output

Bounded code example (external data; do not execute automatically):
```none
I am coro_a(). Hi!
I am coro_a(). Hi!
I am coro_a(). Hi!
I am coro_b(). I sure hope no one hogs the event loop...
```

If we change await coro_a() to await asyncio.create_task(coro_a()), the behavior changes. The coroutine main() cedes control to the event loop with that statement. The event loop then proceeds through its backlog of work, calling task_b and then the task which wraps coro_a() before resuming the coroutine main().

Bounded code example (external data; do not execute automatically):
```none
I am coro_b(). I sure hope no one hogs the event loop...
I am coro_a(). Hi!
I am coro_a(). Hi!
I am coro_a(). Hi!
```

This behavior of await coroutine can trip a lot of people up! That example highlights how using only await coroutine could unintentionally hog control from other tasks and effectively stall the event loop. asyncio.run can help you detect such occurrences via the debug=True flag, which enables debug mode . Among other things, it will log any coroutines that monopolize execution for 100ms or longer. …