asyncio - это, скорее, как обойтись без многопоточности.
Она хорошо работает для ситуаций, когда большая часть времени потока выполнения уходит на ожидание ввода-вывода - например, на ответ по сети. Если у тебя есть что-то, что занимает CPU, или любая другая операция, выполняемая вне рамок ввода-вывода через asyncio - это остановит выполнение асинхронной программы.
Если на пальцах, то асинхронная программа состоит из отдельных корутин. Разница с потоками в том, что потоки переключает ОС, а корутины сами отдают управление, когда уходят в ожидание события (грубо говоря, когда делают вызов с await). Это упрощает синхронизацию корутин, так как ты точно знаешь, когда твоя корутина отдаст управление - меньше шансов налететь на состояние гонки из-за внезапного переключения. Опять же, переключение корутин куда более детерминировано, чем переключение потоков, так что "плавающие" баги встречаются реже.
Тело асинхронной программы - это рабочий цикл (т.н. реактор, он же loop), который проверяет выполняемые операции ввода-вывода, и возобновляет выполнение корутины, когда её операция завершилась. Когда корутина завершится или запланирует ещё одну операцию, она вернёт управление в цикл. Вот почему "задумавшаяся" корутина остановит всю программу.
Минус в том, что в отличие от многопоточности, нельзя просто вжжжжжжух и сделать код асинхронным. Он должен писаться как асинхронный с самого начала, с прицелом на исполнение в реакторе.