Важно понить, что асинхронность это не паралельное выполнение задач, а кооперативная много задачность (то есть все процессы обязаны уступать задачи время другим, это происходит в моменты ожидания операций ввода-вывода.
Самое первое, с чего начинается работа с асинхронным кодом это получение или создание цикла событий. Простыми словами это бесконечный цикл, который из доступных для исполнения задач выбирает одну и ее выполняет, когда задача завершается или передает управлние, цикл выбирает следующую задачу и т.д.
1 2 3 4 5 6 7 8 |
import asyncio if __name__ == "__main__": print('get default even loop (create if not exists)') loop = asyncio.get_event_loop() print('run even loop') loop.run_forever() print('this code is unreachable!') |
Таким образом программа запрашивает цикл событий (функция get_event_loop – получает уже существующий или создает новыйс параметрами по умолчанию), дальше происходит запуск самого цикла метод run_forever. После чего цикл будет работать до тех пор пока его не остновят методом stop.
Более простой вариант с главной асинхронной задачей. Цикл событий создастся сам и будет работать до тех пор пока в цикле не завершатся задача, переданная в качестве параметра функци run.
1 2 3 4 5 6 7 8 9 10 11 12 |
import asyncio async def main(): await asyncio.sleep(5) if __name__ == "__main__": print('create event loop, add one task and run even loop') asyncio.run(main()) print('Program end after 5 seconds') |
Стоит отметить, что порождение новых задач например функцией create_task, никаки не влияет на завершение цикла событий, даже если задачи есть они все равно будет завершены вместе с осноным циклом событий после завершения основного метода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import asyncio import datetime from random import random async def test(time=None): if time is None: time = (10 * random()) print(f"Start sleep task on {time} sec") await asyncio.sleep(time) print(f"END sleep task on {time} sec") async def main(): asyncio.create_task(test()) await asyncio.sleep(5) asyncio.create_task(test()) asyncio.create_task(test()) asyncio.create_task(test()) asyncio.create_task(test()) if __name__ == "__main__": print('create event loop, add one task and run even loop') print("START TIME", datetime.datetime.now().strftime("%X")) asyncio.run(main()) print("END TIME", datetime.datetime.now().strftime("%X")) print('Program end after 5 seconds') |
Вывод этого скрипта будет следующим:
1 2 3 4 5 6 7 8 9 |
create event loop, add one task and run even loop START TIME 13:01:25 Start sleep task on 5.5198401798298296 sec Start sleep task on 3.3347784708795114 sec Start sleep task on 4.415664065195769 sec Start sleep task on 7.809760234107892 sec Start sleep task on 3.381149259565437 sec END TIME 13:01:30 Program end after 5 seconds |
Таким образом по выводу мы видим, что задачи запустились, но завершится они не успели, так как главная задача завершилась и цикл событий был уничтожен, вместе со всеми остальными задачями. Если жи необходимо дождатся завершения всех задач то можно реализовать так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import asyncio import datetime from random import random async def test(time=None): try: if time is None: time = (10 * random()) print(f"Start sleep task on {time} sec") await asyncio.sleep(time) print(f"END sleep task on {time} sec") except Exception as e: print(e) async def main(): tasks = [asyncio.create_task(test())] await asyncio.sleep(5) tasks.append(asyncio.create_task(test())) tasks.append(asyncio.create_task(test())) tasks.append(asyncio.create_task(test())) tasks.append(asyncio.create_task(test())) await asyncio.gather(*tasks) if __name__ == "__main__": print('create event loop, add one task and run even loop') start = datetime.datetime.now() print("START TIME", start.strftime("%X")) asyncio.run(main()) end = datetime.datetime.now() delta = end - start print("END TIME", end.strftime("%X")) print(f'Program end after {delta} seconds') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
create event loop, add one task and run even loop START TIME 13:45:37 Start sleep task on 1.5684484755127182 sec END sleep task on 1.5684484755127182 sec Start sleep task on 4.113557222772673 sec Start sleep task on 3.9307327671383208 sec Start sleep task on 3.631968765868394 sec Start sleep task on 5.75128971457215 sec END sleep task on 3.631968765868394 sec END sleep task on 3.9307327671383208 sec END sleep task on 4.113557222772673 sec END sleep task on 5.75128971457215 sec END TIME 13:45:48 Program end after 0:00:10.750429 seconds Process finished with exit code 0 |
Важно понимать, что запуск под задач, без кода ожидания их завершения пойдет по сценарию возможного досрочного завершения всего, что не успело выполнится если главный процесс завершится раньше.
Однако код ниже выполнит абсолютно все задачи, так как завершение цикла событий не происходит:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import asyncio from random import random # маленький тест который ожидает проивольное время async def test(time=None): if time is None: time = (10 * random()) print(f"Start sleep task on {time} sec") await asyncio.sleep(time) print(f"END sleep task on {time} sec") # основная задача async def main(): asyncio.create_task(test()) await asyncio.sleep(5) asyncio.create_task(test()) asyncio.create_task(test()) asyncio.create_task(test()) asyncio.create_task(test()) if __name__ == "__main__": print('get default even loop (create if not exists)') loop = asyncio.get_event_loop() print('run even loop') loop.create_task(main()) loop.run_forever() print('this code is unreachable!') |
ВАЖНО: не в коем случае не должно происходить блокирование цикла событий, например блокирующими не асинхронными операциями, так как, это оcтановит выполнение всех асинхронных задач.