asyncio uses event loops to orchestrate the callbacks and asynchronous tasks. Event loops live in the context of event loop policies. ![Структура данных буферного кэша](https://whoisdeveloper.ru/static/img/asyncio10.png) Coroutines can be thought of as functions you can “pause” at stages explicitly marked with some sort of syntactical element. The coroutine’s state is tracked via a task object, instantiated by the respective event loop. The event loop keeps track of which task is currently running and delegates CPU time from idling coroutines to a pending one. ### Locating the Currently Running Loop Как узнать выполняется ли event loop в данный момент и какой именно? ``` Используйте asyncio.get_event_loop и asyncio.get_running_loop. ``` Что делает asyncio.get_event_loop под капотом? 1. Проверяет выполняется ли цикл в момент вызова функции. 2. Возвращает запущенный цикл, pid которого соответствует pid текущего процесса, если такой имеется. 3. Если нет, получает thread-global LoopPolicy, который хранится в глобальной переменной в модуле asyncio. 4. Если он не установлен, создает его с помощью Default Loop Policy, используя блокировку. 5. Обратите внимание, что DefaultLoopPolicy зависит от операционной системы и имеет подклассы BaseDefaultEventLoopPolicy, которые предоставляют реализацию loop. get_event_loop по умолчанию, которая вызывается. 6. Вот в чем загвоздка: метод loop_policy.get_event_loop создает экземпляр цикла только в том случае, если вы находитесь в основном потоке, и присваивает его локальной переменной потока. Если вы не находитесь в основном потоке и ни один запущенный цикл не создается другими способами, это вызовет ошибку RuntimeError. asyncio.get_running_loop работает по-другому. Он всегда будет возвращать запущенный в данный момент экземпляр цикла, если он уже запущен. Если такового нет, это вызовет ошибку RuntimeError. ### Attaching a Loop to the Thread Problem Creating one loop per thread that’s bond to the thread and which’s finishing can be also awaited can be a challenging task. Later we learn about the executor API, which allows us to execute blocking coroutine calls as non-blocking calls by executing the respective calls on a thread pool. Solution Using the threading. Thread and the side-effect-free (besides event loop policy creation) asyncio.new_event_loop APIs, we can create thread instances that have unique event loop instances. ``` import asyncio import threading def create_event_loop_thread(worker, *args, **kwargs): def _worker(*args, **kwargs): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: loop.run_until_complete(worker(*args, **kwargs)) finally: loop.close() return threading.Thread(target=_worker, args=args, kwargs=kwargs) async def print_coro(*args, **kwargs): print(f"Inside the print coro on {threading.get_ident()}:", (args, kwargs)) def start_threads(*threads): [t.start() for t in threads if isinstance(t, threading.Thread)] def join_threads(*threads): [t.join() for t in threads if isinstance(t, threading.Thread)] workers = [create_event_loop_thread(print_coro) for i in range(10)] start_threads(*workers) join_threads(*workers) ``` Loops live in the context of a loop policy. The DefaultLoopPolicy scopes the loop per thread and does not allow creation of a loop outside a main thread via asyncio.get_event_loop. Hence, we must create a thread local event loop via asyncio.set_event_loop(asyncio.new_event_loop()). We then await the asyncio.run_until_complete completion inside our internal worker function called _worker by waiting for the thread to be joined via join_threads.