Search code examples
python-3.xpython-asynciocoroutine

When can an asyncio Coroutine be interrupted?


THe question is quite simple. I am new to python's asyncio module and coroutines. If I write an async function like

import asyncio

async def f():
    a = 1
    b = 2

    await asyncio.sleep(10)

    return a + b

Can this coroutine for example be interrupted after the a = 1 and before the b = 2 statement? In actual multithreading of course everything would be asynchronous but as I understand it that is not the case here. So at what statements might a coroutine be interrupted so that the execution returns to the loop? This is for example important when working with locks.


Solution

  • That is what is in the comments: async code in Python is only interrupted when there is an await instruction, or an async for ... or async with ... statement.

    In your particular example, it will not stop between the a = 1 and b = 2 lines. This is the one of the biggest advantages of async programming vs, say, threading: code is deterministic and linear, with the "pause" points explicitly delimited.

    Actually, I've found out the problem is often the converse: it is not always that an await statement shows that the async loop will do a full iteration and give a chance to all running tasks.

    What happens is that ultimately, the "leaf" calls of an asyncio tasks, usually the ones that will actually perform I/O and will have to "wait for an external response" are based in callbacks: they schedule a callback which the asyncio loop is aware of, and the await interface can be used on top of that. And more than one library presenting itself as "async capable" just fake the async part - the inner call, despite being defined with async def is just a normal, sync function. Maybe not in all circumstances, sometimes it is just really async when data is not yet ready to fetch on whatever device it is getting it. For example, a call to read data from a file might be "real" async to read a huge amount of data, but just read data and return synchronously when there is not much to do.

    So, it happens that when there are calls like these, even when there is an await statement in the code, the other tasks don't have a chance to run.

    To ensure the asyncio loop runs, the remedy is them inserting an await asyncio.sleep(0) (yes, zero): that will have the necessary underlying callback interface which ensures the loop will be able to poll everything there is to poll, and execute a step in all paused code that is ready do proceed (there is no such thing as a priority either: every ready task is called in order).

    Anyway, going back to the main subject: in practice, locks are much less necessary in asyncio code (I never needed them so far).