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.