TLDR: No. Coroutines and their respective keywords (await
, async with
, async for
) only enable suspension. Whether suspension occurs depends on the framework used, if at all.
Third-party async functions / iterators / context managers can act as
checkpoints; if you see await <something>
or one of its friends, then
that might be a checkpoint. So to be safe, you should prepare for
scheduling or cancellation happening there.
[Trio documentation]
The await
syntax of Python is syntactic sugar around two fundamental mechanisms: yield
to temporarily suspend with a value, and return
to permanently exit with a value. These are the same that, say, a generator function coroutine can use:
def gencoroutine():
for i in range(5):
yield i # temporarily suspend
return 5 # permanently exit
Notably, return
does not imply a suspension. It is possible for a generator coroutine to never yield
at all.
The await
keyword (and its sibling yield from
) interacts with both the yield
and return
mechanism:
- If its target
yield
s, await
"passes on" the suspension to its own caller. This allows to suspend an entire stack of coroutines that all await
each other.
- If its target
returns
s, await
catches the return value and provides it to its own coroutine. This allows to return a value directly to a "caller", without suspension.
This means that await
does not guarantee that a suspension occurs. It is up to the target of await
to trigger a suspension.
By itself, an async def
coroutine can only return
without suspension, and await
to allow suspension. It cannot suspend by itself (yield
does not suspend to the event loop).
async def unyielding():
return 2 # or `pass`
This means that await
of just coroutines does never suspend. Only specific awaitables are able to suspend.
Suspension is only possible for awaitables with a custom __await__
method. These can yield
directly to the event loop.
class YieldToLoop:
def __await__(self):
yield # to event loop
return # to awaiter
This means that await
, directly or indirectly, of a framework's awaitable will suspend.
The exact semantics of suspending depend on the async framework in use. For example, whether a sleep(0)
triggers a suspension or not, or which coroutine to run instead, is up to the framework. This also extends to async iterators and context managers -- for example, many async context managers will suspend either on enter or exit but not both.
Trio
If you call an async function provided by Trio (await <something in trio>
), and it doesn’t raise an exception, then it always acts as a checkpoint. (If it does raise an exception, it might act as a checkpoint or might not.)
Asyncio
sleep()
always suspends the current task, allowing other tasks to run.