Asyncio best practices

So I thought it’d be great to catalog some of the best practices and patterns to follow while writing asynchronous code in Python, in order to help guide all developers who are planning to use this awesome feature on how to use it right.
What I mention here is not ultimate and might need to be corrected by Devs who have used this feature longer that I have, and also updated with more design patterns.

1)async/await usage

Async functions are not necessarily asynchronous. I’ve noticed that coroutines shall run in order as they have been written in code, which isn’t exactly asynchronous but generally synchronous in nature. For example

import asyncio
import time
async def waiter (secs):
    time.sleep(secs)

async def main():
    await waiter(1)
    await waiter(2)
    await waiter(3)

asyncio.run(main())

Such a program will run from top to bottom just as a synchronous program would. So in order to make it asynchronous, you have to interact with the asyncio library

2) Do not create functions with long running loops

Having function that contain long running loops cause the event loop to halt until the function finishes, which causes lag.
The best way to go around this is to rely on the already existing event loop and just schedule every iteration of your function loop onto the event loop. For example

def long_runner(value):
    loop = asyncio.get_running_loop()
    if (value <= 1_000):
        loop.call_soon(long_runner((value+1)))

3) using tasks

I think using tasks is the equivalent of manually scheduling functions on the event loop since in this case, the tasks shall be tracked by the event loop.
I don’t think I can explain this concept better than @ambv on the series he created about asyncio,

There exists more patterns that we can use and share in order to build really great applications with asyncio, so I’m hoping this topics could act as a catalogue to these patterns and continously kept up-to-date with more current patterns