Does await always give other tasks a chance to execute?

前端 未结 2 1636
北恋
北恋 2020-12-18 10:57

I\'d like to know what guarantees python gives around when a event loop will switch tasks.

As I understand it async / await are significant

2条回答
  •  借酒劲吻你
    2020-12-18 11:46

    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 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 yields, 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 returnss, 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 ), 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.

提交回复
热议问题