Цикл событийный¶
Предисловие
Цикл событий — это ядро любого asyncio приложения. Циклы событий запускают асинхронные задачи и обратные вызовы, выполняют сетевые операции ввода-вывода и запускают подпроцессы.
Разработчики приложений обычно должны использовать высокоуровневые функции
asyncio, такие как asyncio.run()
, и им редко нужно ссылаться на объект
цикла или вызывать его методы. Раздел предназначен в основном для авторов
низкоуровневого кода, библиотек и фреймворков, которым требуется более тонкий
контроль над поведением цикла событий.
Получение цикла событий
Следующие ниже низкоуровневые функции могут использоваться для получения, установки или создания цикла событий:
-
asyncio.
get_running_loop
()¶ Вернуть запущенный цикл событий в текущем потоке ОС.
Если нет запущенного цикла событий, возникает
RuntimeError
. Эта функция может вызвана только из корутины или обратного вызова.Добавлено в версии 3.7.
-
asyncio.
get_event_loop
()¶ Получить текущий цикл событий.
Если в текущем потоке ОС не установлен текущий цикл событий, поток ОС является основным, а
set_event_loop()
ещё не был вызван, asyncio создаст новый цикл событий и установит его как текущий.Поскольку у этой функции довольно сложное поведение (особенно при использовании настраиваемых политик цикла событий), использование функции
get_running_loop()
предпочтительнее, чемget_event_loop()
в корутинах и обратных вызовах.Также рассмотрите возможность использования функции
asyncio.run()
вместо использования функций нижнего уровня для создания и закрытия цикла событий вручную.
-
asyncio.
set_event_loop
(loop)¶ Установить loop как текущий цикл событий для текущего потока ОС.
-
asyncio.
new_event_loop
()¶ Создать новый объект цикла событий.
Обратите внимание, что поведение функций get_event_loop()
,
set_event_loop()
и new_event_loop()
можно изменить с помощью
установки настраиваемой политики цикла событий.
Содержание
Данная страница документации содержит следующие разделы:
- Раздел Методы цикла событий — справочная документация по API- интерфейсам цикла событий;
- Раздел Дескрипторы обратного вызова документирует
экземпляры
Handle
иTimerHandle
, которые возвращаются такими методами планирования, какloop.call_soon()
иloop.call_later()
; - В разделе Server объекты описаны типы, возвращаемые такими
методами цикла событий, как
loop.create_server()
; - Раздел Реализации цикла событий документирует классы
SelectorEventLoop
иProactorEventLoop
; - Раздел Примеры демонстрирует, как работать с некоторыми API-интерфейсами цикла событий.
Методы цикла событий¶
У циклов событий есть низкоуровневое API для следующих целей:
- Запуск и остановка цикла
- Планирование обратных вызовов
- Планирование отложенных обратных вызовов
- Создание Футур и Задач
- Открытие сетевых подключёний
- Создание сетевых серверов
- Передача файлов
- Обновление TLS
- Наблюдение за файловыми дескрипторами
- Непосредственная работа с объектами сокетов
- DNS
- Работа с трубами
- Unix сигналы
- Выполнение кода в пулах потоков или процессов
- API обработки ошибок
- Включение режима отладки
- Запущенные подпроцессы
Запуск и остановка цикла¶
-
loop.
run_until_complete
(future)¶ Работать, пока не завершится future (экземпляр
Future
).Если аргумент — объект корутины, он неявно планируется для запуска как
asyncio.Task
.Вернуть результат Future или вызвать его исключение.
-
loop.
run_forever
()¶ Запустить цикл обработки событий, пока не будет вызван
stop()
.Если
stop()
вызывается до вызоваrun_forever()
, цикл опрашивает селектор ввода-вывода один раз с нулевым таймаутом, запускает все обратные вызовы, запланированные в ответ на события ввода-вывода (и те, которые уже были запланированы), а затем завершается.Если
stop()
вызывается во время работыrun_forever()
, цикл выполнит текущий пакет обратных вызовов и затем завершится. Обратите внимание, что новые обратные вызовы, запланированные обратными вызовами, в этом случае не будут выполняться; вместо этого они будут запущены при следующем вызовеrun_forever()
илиrun_until_complete()
.
-
loop.
stop
()¶ Остановить цикл событий.
-
loop.
is_running
()¶ Вернуть
True
, если цикл обработки событий выполняется в данный момент.
-
loop.
is_closed
()¶ Вернуть
True
, если цикл событий был закрыт.
-
loop.
close
()¶ Закрыть цикл событий.
При вызове этой функции цикл не должен выполняться. Все ожидающие обратные вызовы будут отброшены.
Метод очищает все очереди и завершает работу исполнителя, но не дожидается завершения работы исполнителя.
Метод идемпотентен и необратим. Никакие другие методы не должны вызываться после закрытия цикла событий.
-
coroutine
loop.
shutdown_asyncgens
()¶ Запланировать закрытие всех открытых в данный момент объектов асинхронного генератора с помощью вызова
aclose()
. После вызова данного метода цикл событий выдаст предупреждение, если итерируется новый асинхронный генератор. Его следует использовать для надежного завершения всех запланированных асинхронных генераторов.Обратите внимание, что при использовании
asyncio.run()
нет необходимости вызывать эту функцию.Пример:
try: loop.run_forever() finally: loop.run_until_complete(loop.shutdown_asyncgens()) loop.close()
Добавлено в версии 3.6.
Планирование обратных вызовов¶
-
loop.
call_soon
(callback, *args, context=None)¶ Запланировать callback колбэк, который будет вызываться с аргументами args на следующей итерации цикла событий.
Обратные вызовы (Callbacks) вызываются в том порядке, в котором они зарегистрированы. Каждый обратный вызов будет вызываться ровно один раз.
Необязательный ключевой аргумент context, позволяет указать пользовательский
contextvars.Context
для выполнения callback. Используется текущий контекст, когда context не предоставлен.Возвращается экземпляр
asyncio.Handle
, который можно использовать позже для отмены обратного вызова.Метод не является потокобезопасным.
-
loop.
call_soon_threadsafe
(callback, *args, context=None)¶ Потокобезопасный вариант
call_soon()
. Должен использоваться для планирования обратных вызовов из другого потока.См. раздел документации конкурентность и многопоточность.
Изменено в версии 3.7: Был добавлен параметр context только для ключевых параметров. См. PEP 567 для получения более подробной информации.
Примечание
Большинство функций планирования asyncio
не позволяют передавать
ключевые аргументы. Для этого используйте functools.partial()
:
# запланировать "print("Hello", flush=True)"
loop.call_soon(
functools.partial(print, "Hello", flush=True))
Использование частичных объектов обычно более удобно, чем использование лямбда-выражений, поскольку asyncio может лучше отображать частичные объекты в сообщениях об отладке и ошибках.
Планирование отложенных обратных вызовов¶
Цикл событий предоставляет механизмы для планирования функций обратного вызова, которые будут вызываться в какой-то момент в будущем. Цикл событий использует монотонные часы для отслеживания времени.
-
loop.
call_later
(delay, callback, *args, context=None)¶ Запланировать callback, который будет вызываться после заданного количества секунд delay (может быть либо int, либо float).
Возвращается экземпляр
asyncio.TimerHandle
, который можно использовать для отмены обратного вызова.callback будет вызван ровно один раз. Если два обратных вызова запланированы на одно и то же время, порядок, в котором они вызываются, не определён.
Необязательные позиционные args будут переданы обратному вызову при его вызове. Если вы хотите, чтобы callback вызывался с ключевыми аргументами, используйте
functools.partial()
.Необязательный ключевой аргумент context, позволяет указать пользовательский
contextvars.Context
для выполнения callback. Используется текущий контекст, когда context не предоставлен.Изменено в версии 3.7: Был добавлен только ключевой параметр context. См. PEP 567 для получения более подробной информации.
Изменено в версии 3.8: В Python 3.7 и более ранних версиях с реализацией цикла событий по умолчанию delay не могла превышать одного дня. Это было исправлено в Python 3.8.
-
loop.
call_at
(when, callback, *args, context=None)¶ Запланировать callback, который будет вызываться в заданную абсолютную метку времени, when (int или float), используя ту же временную ссылку, что и
loop.time()
.Поведение данного метода такое же, как у
call_later()
.Возвращается экземпляр
asyncio.TimerHandle
, который можно использовать для отмены обратного вызова.Изменено в версии 3.7: Был добавлен только ключевой параметр context. См. PEP 567 для получения более подробной информации.
Изменено в версии 3.8: В Python 3.7 и ранее с реализацией цикла событий по умолчанию разница между when и текущим временем не могла превышать одного дня. Это было исправлено в Python 3.8.
-
loop.
time
()¶ Возвращает текущее время в виде значения
float
в соответствии с внутренними монотонными часами цикла событий.
Примечание
Изменено в версии 3.8: В Python 3.7 и более ранних версиях таймауты (относительная delay или абсолютная when) не должны превышать одного дня. Это было исправлено в Python 3.8.
См.также
Функция asyncio.sleep()
.
Создание Футур и Задач¶
-
loop.
create_future
()¶ Создать объект
asyncio.Future
, прикрепленный к циклу событий.Это предпочтительный способ создания Футур (Futures) в asyncio. Он позволяет сторонним циклам событий предоставлять альтернативные реализации объекта Футуры (с лучшей производительностью или инструментарием).
Добавлено в версии 3.5.2.
-
loop.
create_task
(coro, *, name=None)¶ Запланировать выполнение корутины. Вернуть объект
Task
.Сторонние циклы событий могут использовать собственный подкласс
Task
для взаимодействия. В этом случае тип результата является подклассомTask
.Если указан аргумент name, а не
None
, он устанавливается как имя задачи с использованиемTask.set_name()
.Изменено в версии 3.8: Добавлен параметр
name
.
-
loop.
set_task_factory
(factory)¶ Задать фабрику задач, которая будет использоваться
loop.create_task()
.Если factory —
None
, будет установлена фабрика задач по умолчанию. В противном случае factory должен быть вызываемым с сигнатурой, соответствующей(loop, coro)
, где loop — это ссылка на активный цикл событий, а coro — объект корутины. Вызываемый объект должен возвращатьasyncio.Future
-совместимый объект.
-
loop.
get_task_factory
()¶ Вернуть фабрику задач или
None
, если используется фабрика по умолчанию.
Открытие сетевых подключёний¶
-
coroutine
loop.
create_connection
(protocol_factory, host=None, port=None, *, ssl=None, family=0, proto=0, flags=0, sock=None, local_addr=None, server_hostname=None, ssl_handshake_timeout=None, happy_eyeballs_delay=None, interleave=None)¶ Открыть соединение потокового транспорта с заданным адресом, указанным host и port.
Семейство сокетов может быть
AF_INET
илиAF_INET6
в зависимости от host (или аргумента family, если он указан).Тип сокета будет
SOCK_STREAM
.protocol_factory должен быть вызываемым, возвращающим реализацию протокола asyncio.
Метод попытается установить соединение в фоновом режиме. В случае успеха возвращается пара
(transport, protocol)
.Хронологический синопсис основной операции выглядит следующим образом:
- Соединение установлено, и для него создаётся транспорт.
- protocol_factory вызывается без аргументов и ожидается, что он вернёт экземпляр протокола.
- Экземпляр протокола связывается с транспортом путём вызова его метода
connection_made()
. - В случае успеха возвращается кортеж
(transport, protocol)
.
Созданный транспорт является зависящим от реализации двунаправленным потоком.
Другие аргументы:
ssl: если задано, а не false, создаётся SSL/TLS транспорт (по умолчанию создаётся простой TCP транспорт). Если ssl является объектом
ssl.SSLContext
, контекст используется для создания транспорта; если ssl —True
, используется контекст по умолчанию, возвращенный изssl.create_default_context()
.См.также
server_hostname устанавливает или переопределяет имя хоста, с которым будет сопоставляться сертификат целевого сервера. Следует передавать, только если ssl не
None
. По умолчанию используется значение аргумента host. Если host пуст, по умолчанию нет, и вы должны передать значение server_hostname. Если server_hostname является пустой строкой, сопоставление имён хостов отключено (что представляет собой серьезную угрозу безопасности, допускающую потенциальные атаки типа «злоумышленник в середине»).family, proto, flags — это необязательное семейство адресов, протокол и флаги, которые необходимо передать getaddrinfo() для разрешения host. Если задано, все они должны быть целыми числами из соответствующих констант модуля
socket
.happy_eyeballs_delay, если задан, включает Happy Eyeballs для этого соединения. Это должно быть число с плавающей запятой, представляющее количество времени в секундах для ожидания завершения попытки подключения перед запуском следующей попытки параллельно. Это «Задержка попытки подключения», как определено в RFC 8305. Разумное значение по умолчанию, рекомендованное RFC —
0.25
(250 миллисекунд).interleave управляет переупорядочиванием адресов, когда имя хоста преобразуется в несколько IP-адресов. Если
0
или не указано, переупорядочивание не выполняется, и адреса проверяются в порядке, возвращаемомgetaddrinfo()
. Если задано положительное целое число, адреса чередуются по семейству адресов, и данное целое число интерпретируется как «Счетчик первого семейства адресов», как определено в RFC 8305. По умолчанию используется0
, если happy_eyeballs_delay не указан, и1
, если он указан.sock, если он задан, должен быть существующим, уже подключённым объектом
socket.socket
, который будет использоваться транспортом. Если указан sock, не следует указывать ни host, port, family, proto, flags, happy_eyeballs_delay, interleave, ни local_addr.local_addr, если он задан, представляет собой кортеж
(local_host, local_port)
, используемый для локальной привязки сокета. local_host и local_port ищутся с помощьюgetaddrinfo()
, аналогично host и port.ssl_handshake_timeout — (для TLS-соединения) время в секундах, в течение которого нужно дождаться завершения подтверждения TLS, прежде чем разорвать соединение.
60.0
секунда, еслиNone
(по умолчанию).
Добавлено в версии 3.8: Добавлены параметры happy_eyeballs_delay и interleave.
Алгоритм Happy Eyeballs: успех с хостами двойного стека. Когда путь и протокол IPv4 сервера работают, но путь и протокол IPv6 сервера не работают, клиентское приложение с двойным стеком испытывает значительную задержку соединения по сравнению с клиентом, работающим только с IPv4. Это нежелательно, потому что из-за этого клиент с двойным стеком будет хуже работать с пользователем. Документ определяет требования к алгоритмам, которые уменьшают эту видимую для пользователя задержку, и предоставляет алгоритм.
Для получения дополнительной информации rfc6555
Добавлено в версии 3.7: Параметр ssl_handshake_timeout.
Изменено в версии 3.6: Параметр сокета
TCP_NODELAY
установлен по умолчанию для всех TCP-соединений.Изменено в версии 3.5: Добавлена поддержка SSL/TLS в
ProactorEventLoop
.См.также
Функция
open_connection()
— это альтернативный API высокого уровня. Он возвращает пару (StreamReader
,StreamWriter
), которые можно использовать непосредственно в коде async/await.
-
coroutine
loop.
create_datagram_endpoint
(protocol_factory, local_addr=None, remote_addr=None, *, family=0, proto=0, flags=0, reuse_address=None, reuse_port=None, allow_broadcast=None, sock=None)¶ Примечание
Параметр reuse_address больше не поддерживается, поскольку использование
SO_REUSEADDR
представляет серьёзную проблему безопасности для UDP. Явная передачаreuse_address=True
вызовет исключение.Когда несколько процессов с разными UID назначают сокеты одинаковому адресу сокета UDP с
SO_REUSEADDR
, входящие пакеты могут случайным образом распределяться между сокетами.Для поддерживаемых платформ reuse_port может использоваться как замена аналогичной функциональности. С reuse_port вместо него используется
SO_REUSEPORT
, что специально предотвращает назначение сокетов процессам с разными UID на один и тот же адрес сокета.Создать дейтаграммное соединение.
Семейство сокетов может быть
AF_INET
,AF_INET6
илиAF_UNIX
, в зависимости от host (или аргумента family, если он предоставлен).Тип сокета будет
SOCK_DGRAM
.protocol_factory должен быть вызываемым, возвращающим реализацию протокола.
В случае успеха возвращается кортеж
(transport, protocol)
.Другие аргументы:
- local_addr, если он задан, представляет собой кортеж
(local_host, local_port)
, используемый для локальной привязки сокета. local_host и local_port ищутся с помощьюgetaddrinfo()
. - remote_addr, если он задан, представляет собой кортеж
(remote_host, remote_port)
, используемый для подключения сокета к удаленному адресу. remote_host и remote_port ищутся с помощьюgetaddrinfo()
. - family, proto, flags — это необязательное семейство адресов,
протокол и флаги, которые необходимо передать в
getaddrinfo()
для разрешения host. Если задано, все они должны быть целыми числами из соответствующих констант модуляsocket
. - reuse_port сообщает ядру разрешить привязку этой конечной точки к тому же
порту, к которому привязаны другие существующие конечные точки, при
условии, что все они устанавливают данный флаг при создании. Параметр
не поддерживается в Windows и некоторых системах Unix. Если константа
SO_REUSEPORT
не определена, эта возможность не поддерживается. - allow_broadcast сообщает ядру разрешить этой конечной точке отправлять сообщения на широковещательный адрес.
- sock можно дополнительно указать, чтобы использовать уже существующий,
уже подключённый объект
socket.socket
, который будет использоваться транспортом. Если указано, local_addr и remote_addr следует пропустить (должно бытьNone
).
См. примеры Клиентский эхо протокол UDP и Протокол эхо-сервера UDP.
Изменено в версии 3.4.4: Были добавлены параметры family, proto, flags, reuse_address, reuse_port, allow_broadcast и sock.
Изменено в версии 3.8.1: Параметр reuse_address больше не поддерживается из соображений безопасности.
Изменено в версии 3.8: Добавлена поддержка Windows.
- local_addr, если он задан, представляет собой кортеж
-
coroutine
loop.
create_unix_connection
(protocol_factory, path=None, *, ssl=None, sock=None, server_hostname=None, ssl_handshake_timeout=None)¶ Создать соединение Unix.
Семейство сокетов будет
AF_UNIX
; тип сокета будетSOCK_STREAM
.В случае успеха возвращается кортеж
(transport, protocol)
.path — это имя сокета домена Unix и является обязательным, если не указан параметр sock. Поддерживаются абстрактные сокеты Unix, пути
str
,bytes
иPath
.См. документацию метода
loop.create_connection()
для получения информации об аргументах этого метода.Доступность: Unix.
Добавлено в версии 3.7: Параметр ssl_handshake_timeout.
Изменено в версии 3.7: Параметр path теперь может быть путеподобным объектом.
Создание сетевых серверов¶
-
coroutine
loop.
create_server
(protocol_factory, host=None, port=None, *, family=socket.AF_UNSPEC, flags=socket.AI_PASSIVE, sock=None, backlog=100, ssl=None, reuse_address=None, reuse_port=None, ssl_handshake_timeout=None, start_serving=True)¶ Создать TCP-сервер (тип сокета
SOCK_STREAM
), прослушивающий port адреса host.Возвращает объект
Server
.Аргументы:
- protocol_factory должен быть вызываемым, возвращающим реализацию протокола.
- Параметр host может иметь несколько типов, которые определяют, где сервер
будет прослушивать:
- Если host является строкой, TCP-сервер привязан к одному сетевому интерфейсу, указанному host.
- Если host представляет собой последовательность строк, TCP-сервер привязан ко всем сетевым интерфейсам, указанным в этой последовательности.
- Если host — пустая строка или
None
, предполагаются все интерфейсы, и будет возвращен список из нескольких сокетов (скорее всего, один для IPv4, а другой — для IPv6).
- Для family можно задать значение
socket.AF_INET
илиAF_INET6
, чтобы сокет использовал IPv4 или IPv6. Если не установлен, family будет определяться по имени хоста (по умолчаниюAF_UNSPEC
). - flags — это битовая маска для
getaddrinfo()
. - sock можно дополнительно указать для использования уже существующего объекта сокета. Если указано, host и port указывать не нужно.
- backlog — это максимальное количество подключёний в очереди, переданных
listen()
(по умолчанию 100). - ssl может быть установлен на экземпляр
SSLContext
, чтобы включить TLS по принятым соединениям. - reuse_address указывает ядру повторно использовать локальный сокет в
состоянии
TIME_WAIT
, не дожидаясь истечения его естественного тайм- аута. Если не указано, будет автоматически установлено значениеTrue
в Unix. - reuse_port сообщает ядру разрешить привязку этой конечной точки к тому же порту, к которому привязаны другие существующие конечные точки, при условии, что все они устанавливают данный флаг при создании. Эта опция не поддерживается в Windows.
- ssl_handshake_timeout — (для TLS-сервера) время в секундах, в
течение которого необходимо дождаться завершения рукопожатия TLS, прежде
чем разорвать соединение.
60.0
секунды, еслиNone
(по умолчанию). - start_serving, установленный на
True
(по умолчанию), заставляет созданный сервер немедленно начать принимать соединения. Если установлено значениеFalse
, пользователь должен ждать наServer.start_serving()
илиServer.serve_forever()
, чтобы сервер начал принимать соединения.
Добавлено в версии 3.7: Добавлены параметры ssl_handshake_timeout и start_serving.
Изменено в версии 3.6: Параметр сокета
TCP_NODELAY
установлен по умолчанию для всех TCP-соединений.Изменено в версии 3.5: Добавлена поддержка SSL/TLS в
ProactorEventLoop
.Изменено в версии 3.5.1: Параметр host может быть последовательностью строк.
См.также
Функция
start_server()
— это альтернативный API более высокого уровня, который возвращает паруStreamReader
иStreamWriter
, которые можно использовать в коде async/await.
-
coroutine
loop.
create_unix_server
(protocol_factory, path=None, *, sock=None, backlog=100, ssl=None, ssl_handshake_timeout=None, start_serving=True)¶ Аналогичен
loop.create_server()
, но работает с семейством сокетовAF_UNIX
.path — это имя сокета домена Unix, и он является обязательным, если не указан аргумент sock. Поддерживаются абстрактные сокеты Unix, пути
str
,bytes
иPath
.См. документацию метода
loop.create_server()
для получения информации об аргументах этого метода.Доступность: Unix.
Добавлено в версии 3.7: Параметры ssl_handshake_timeout и start_serving.
Изменено в версии 3.7: Параметр path теперь может быть объектом
Path
.
-
coroutine
loop.
connect_accepted_socket
(protocol_factory, sock, *, ssl=None, ssl_handshake_timeout=None)¶ Обернуть уже принятое соединение в пару транспорт/протокол.
Метод может использоваться серверами, которые принимают соединения вне asyncio, но используют asyncio для их обработки.
Параметры:
- protocol_factory должен быть вызываемым, возвращающим реализацию протокола.
- sock — это уже существующий объект сокета, возвращенный из
socket.accept
. - ssl можно установить на
SSLContext
, чтобы включить SSL через принятые соединения. - ssl_handshake_timeout — это (для SSL-соединения) время в секундах, в
течение которого нужно дождаться завершения подтверждения SSL, прежде чем
разорвать соединение.
60.0
секунды, еслиNone
(по умолчанию).
Возвращает пару
(transport, protocol)
.Добавлено в версии 3.7: Параметр ssl_handshake_timeout.
Добавлено в версии 3.5.3.
Передача файлов¶
-
coroutine
loop.
sendfile
(transport, file, offset=0, count=None, *, fallback=True)¶ Отправить file через transport. Возвращает общее количество отправленных байтов.
В методе используется высокопроизводительный
os.sendfile()
, если он доступен.file должен быть обычным файловым объектом, открытым в двоичном режиме.
offset указывает, с чего начать чтение файла. Если указано, count — это общее количество байтов для передачи в отличие от отправки файла до достижения EOF. Положение файла всегда обновляется, даже если данный метод вызывает ошибку, и
file.tell()
можно использовать для получения фактического количества отправленных байтов.fallback, установленный на
True
, заставляет asyncio вручную читать и отправлять файл, когда платформа не поддерживает системный вызов sendfile (например, Windows или сокет SSL в Unix).Поднять
SendfileNotAvailableError
, если система не поддерживает системный вызов sendfile и fallback —False
.Добавлено в версии 3.7.
Обновление TLS¶
-
coroutine
loop.
start_tls
(transport, protocol, sslcontext, *, server_side=False, server_hostname=None, ssl_handshake_timeout=None)¶ Обновить существующее транспортное соединение до TLS.
Вернуть новый транспортный экземпляр, который protocol должен сразу начать использовать после ожидания (await). Экземпляр transport, переданный методу start_tls. Повторно никогда не должен использоваться.
Параметры:
- экземпляры transport и protocol, возвращаемые такими методами, как
create_server()
иcreate_connection()
. - sslcontext: настроенный экземпляр
SSLContext
. - server_side передаёт
True
при обновлении соединения на стороне сервера (например, созданногоcreate_server()
). - server_hostname: устанавливает или переопределяет имя хоста, с которым будет сопоставляться сертификат целевого сервера.
- ssl_handshake_timeout — (для TLS-соединения) время в секундах, в
течение которого нужно дождаться завершения рукопожатия TLS, прежде чем
разорвать соединение.
60.0
секунд, еслиNone
(по умолчанию).
Добавлено в версии 3.7.
- экземпляры transport и protocol, возвращаемые такими методами, как
Наблюдение за файловыми дескрипторами¶
-
loop.
add_reader
(fd, callback, *args)¶ Начать мониторинг дескриптора файла fd на предмет доступности для чтения и вызвать callback с указанными аргументами, как только fd станет доступным для чтения.
-
loop.
remove_reader
(fd)¶ Прекратить отслеживать дескриптор файла fd на предмет доступности для чтения.
-
loop.
add_writer
(fd, callback, *args)¶ Начать мониторинг дескриптора файла fd на предмет доступности записи и вызвать callback с указанными аргументами, как только fd станет доступным для записи.
Для для передачи ключевых аргументов callback используйте
functools.partial()
.
-
loop.
remove_writer
(fd)¶ Прекратить отслеживать дескриптор файла fd на предмет доступности записи.
См. также раздел Поддержка платформы для ознакомления с некоторыми ограничениями этих методов.
Непосредственная работа с объектами сокетов¶
В общем, реализации протокола, использующие API на основе транспорта, такие как
loop.create_connection()
и loop.create_server()
, работают быстрее,
чем реализации, которые работают с сокетами напрямую. Однако бывают случаи,
когда производительность не критична и удобнее работать напрямую с объектами
socket
.
-
coroutine
loop.
sock_recv
(sock, nbytes)¶ Получить до nbytes от sock. Асинхронная версия
socket.recv()
.Вернуть полученные данные в виде байтового объекта.
sock должен быть неблокирующим сокетом.
Изменено в версии 3.7: Несмотря на то, что метод всегда был документирован как метод корутин, выпуски до Python 3.7 возвращали
Future
. Начиная с Python 3.7 это методasync def
.
-
coroutine
loop.
sock_recv_into
(sock, buf)¶ Получать данные из sock в буфер buf. Смоделирована по методу блокировки
socket.recv_into()
.Возвращает количество байтов, записанных в буфер.
sock должен быть неблокирующим сокетом.
Добавлено в версии 3.7.
-
coroutine
loop.
sock_sendall
(sock, data)¶ Отправить data в сокет sock. Асинхронная версия
socket.sendall()
.Метод продолжает отправку в сокет до тех пор, пока не будут отправлены все data в данных или пока не возникнет ошибка.
None
возвращается в случае успеха. При ошибке возникает исключение. Кроме того, невозможно определить, сколько данных, если таковые имеются, было успешно обработано принимающей стороной соединения.sock должен быть неблокирующим сокетом.
Изменено в версии 3.7: Несмотря на то, что метод всегда документировался как метод корутины, до Python 3.7 он возвращал
Future
. Начиная с Python 3.7, это методasync def
.
-
coroutine
loop.
sock_connect
(sock, address)¶ Подключить sock к удаленному сокету по address.
Асинхронная версия
socket.connect()
.sock должен быть неблокирующим сокетом.
Изменено в версии 3.5.2:
address
больше не требует решения.sock_connect
попытается проверить, разрешен ли уже address, вызвав вsocket.inet_pton()
. В противном случае для разрешения address будет использоватьсяloop.getaddrinfo()
.См.также
-
coroutine
loop.
sock_accept
(sock)¶ Принять соединение. Смоделирована по методу блокировки
socket.accept()
.Сокет должен быть привязан к адресу и прослушивать соединения. Возвращаемое значение — пара
(conn, address)
, где conn — это новый объект сокета, используемый для отправки и получения данных в соединении, а address — это адрес, привязанный к сокету на другом конце соединения.sock должен быть неблокирующим сокетом.
Изменено в версии 3.7: Несмотря на то, что метод всегда документировался как метод корутины, до Python 3.7 он возвращал
Future
. Начиная с Python 3.7, это методasync def
.См.также
-
coroutine
loop.
sock_sendfile
(sock, file, offset=0, count=None, *, fallback=True)¶ Если возможно, отправить файл с помощью высокопроизводительного
os.sendfile
. Возвращает общее количество отправленных байтов.Асинхронная версия
socket.sendfile()
.sock должен быть неблокирующим
socket.SOCK_STREAM
socket
.file должен быть обычным файловым объектом, открытым в двоичном режиме.
offset указывает, с чего начать чтение файла. Если указано, count — это общее количество байтов для передачи в отличие от отправки файла до достижения EOF. Положение файла всегда обновляется, даже если данный метод вызывает ошибку, и
file.tell()
можно использовать для получения фактического количества отправленных байтов.fallback, если задано значение
True
, заставляет asyncio вручную читать и отправлять файл, когда платформа не поддерживает системный вызов sendfile (например, Windows или сокет SSL в Unix).Поднять
SendfileNotAvailableError
, если система не поддерживает системный вызов sendfile, а fallback —False
.sock должен быть неблокирующим сокетом.
Добавлено в версии 3.7.
DNS¶
-
coroutine
loop.
getaddrinfo
(host, port, *, family=0, type=0, proto=0, flags=0)¶ Асинхронная версия
socket.getaddrinfo()
.
-
coroutine
loop.
getnameinfo
(sockaddr, flags=0)¶ Асинхронная версия
socket.getnameinfo()
.
Изменено в версии 3.7: Оба метода getaddrinfo и getnameinfo всегда были задокументированы для
возврата корутины (корутины), но до Python 3.7 они фактически возвращали объекты
asyncio.Future
. Начиная с Python 3.7 оба метода являются
корутинами.
Работа с трубами¶
-
coroutine
loop.
connect_read_pipe
(protocol_factory, pipe)¶ Зарегистрировать конец pipe для чтения в цикле событий.
protocol_factory должен быть вызываемым, возвращающим реализацию asyncio протокола.
pipe — файловый объект.
Возвращает пару
(transport, protocol)
, где transport поддерживает интерфейсReadTransport
, а protocol — это объект, созданный с помощью protocol_factory.В цикле событий
SelectorEventLoop
pipe устанавливается в неблокирующий режим.
-
coroutine
loop.
connect_write_pipe
(protocol_factory, pipe)¶ Зарегистрировать конец pipe для записи в цикле событий.
protocol_factory должен быть вызываемым, возвращающим реализацию протокол asyncio.
pipe — файловый объект.
Возвращает пару
(transport, protocol)
, где transport поддерживает интерфейсWriteTransport
, а protocol — это объект, созданный с помощью protocol_factory.В цикле событий
SelectorEventLoop
pipe устанавливается в неблокирующий режим.
Примечание
SelectorEventLoop
не поддерживает вышеуказанные методы в Windows.
Вместо этого для Windows использовать ProactorEventLoop
.
См.также
Unix сигналы¶
-
loop.
add_signal_handler
(signum, callback, *args)¶ Установить callback в качестве обработчика сигнала signum.
Обратный вызов будет вызываться циклом вместе с другими обратными вызовами в очереди и запускаемыми корутинами этого цикла событий. В отличие от обработчиков сигналов, зарегистрированных с помощью
signal.signal()
, обратный вызов, зарегистрированный с помощью этой функции, может взаимодействовать с циклом событий.Поднять
ValueError
, если номер сигнала недействителен или не обнаруживается. ПоднятьRuntimeError
, если есть проблема с настройкой обработчика.Используйте
functools.partial()
для передачи ключевых аргументов для callback.Как и
signal.signal()
, эта функция должна вызываться в основном потоке.
-
loop.
remove_signal_handler
(sig)¶ Удалить обработчик сигнала sig.
Вернуть
True
, если обработчик сигнала был удален, илиFalse
, если обработчик не был установлен для данного сигнала.Доступность: Unix.
См.также
Модуль signal
.
Выполнение кода в пулах потоков или процессов¶
-
awaitable
loop.
run_in_executor
(исполнитель, функция, аргументы)¶ Организовать func функции в указанном исполнителе.
Аргумент executor должен быть экземпляром
concurrent.futures.Executor
. executor по умолчанию используется, если исполнителем являетсяNone
.Пример:
import asyncio import concurrent.futures def blocking_io(): # Файловые операции (такие как журналирование) могут блокировать # событийный цикл: запустив их в пуле потоков. with open('/dev/urandom', 'rb') as f: return f.read(100) def cpu_bound(): # Связанные с процессором операции блокируют событийного цикла обработки: # в общем, лучше запускать их в # пуле процессов. return sum(i * i for i in range(10 ** 7)) async def main(): loop = asyncio.get_running_loop() ## Опции: # 1. Запустить в исполнителе цикла по умолчанию: result = await loop.run_in_executor( None, blocking_io) print('default thread pool', result) # 2. Запустить в пользовательском пуле потоков: with concurrent.futures.ThreadPoolExecutor() as pool: result = await loop.run_in_executor( pool, blocking_io) print('custom thread pool', result) # 3. Запустить в пользовательском пуле процессов: with concurrent.futures.ProcessPoolExecutor() as pool: result = await loop.run_in_executor( pool, cpu_bound) print('custom process pool', result) asyncio.run(main())
Метод возвращает объект
asyncio.Future
.Используйте
functools.partial()
для передачи ключевых аргументов для func.Изменено в версии 3.5.3:
loop.run_in_executor()
больше не настраиваетmax_workers
созданного им исполнителя пула потоков, вместо этого оставляя его исполнителю пула потоков (ThreadPoolExecutor
) для установки значения по умолчанию.
-
loop.
set_default_executor
(executor)¶ Установить executor в качестве исполнителя по умолчанию, используемого
run_in_executor()
. executor должен быть экземпляромThreadPoolExecutor
.Не рекомендуется, начиная с версии 3.8: Использование исполнителя, не являющегося экземпляром
ThreadPoolExecutor
, устарело и вызовет ошибку в Python 3.9.executor должен быть экземпляром
concurrent.futures.ThreadPoolExecutor
.
API обработки ошибок¶
Позволяет настроить обработку исключений в цикле событий.
-
loop.
set_exception_handler
(handler)¶ Установить handler как новый обработчик исключений цикла событий.
Если handler является
None
, будет установлен обработчик исключений по умолчанию. В противном случае handler должен быть вызываемым с сигнатурой, соответствующей(loop, context)
, гдеloop
— это ссылка на активный цикл событий, аcontext
— это объектdict
, содержащий сведения об исключении (подробные сведения о контексте см. в документацииcall_exception_handler()
).
-
loop.
get_exception_handler
()¶ Вернуть текущий обработчик исключений или
None
, если пользовательский обработчик исключений не был установлен.Добавлено в версии 3.5.2.
-
loop.
default_exception_handler
(context)¶ Обработчик исключений по умолчанию.
Вызывается, когда возникает исключение, и обработчик исключений не установлен. Это может быть вызвано настраиваемым обработчиком исключений, который хочет отложить поведение обработчика по умолчанию.
context параметр имеет то же значение, что и в
call_exception_handler()
.
-
loop.
call_exception_handler
(context)¶ Вызов текущего обработчика исключения цикла событий.
context — это объект
dict
, содержащий следующие ключи (новые ключи могут быть введены в будущих версиях Python):- «message»: сообщение об ошибке;
- «exception» (необязательно): объект исключения;
- «future» (необязательно): экземпляр
asyncio.Future
; - «handle» (необязательно): экземпляр
asyncio.Handle
; - «protocol» (необязательно): экземпляр Протокола;
- «transport» (необязательно): экземпляр Транспорта;
- «socket» (необязательно): экземпляр
socket.socket
.
Примечание
Метод не следует перегружать в циклах событий подкласса. Для настраиваемой обработки исключений использовать метод
set_exception_handler()
.
Включение режима отладки¶
-
loop.
get_debug
()¶ Получить режим отладки (
bool
) цикла событий.Значение по умолчанию —
True
, если для переменной средыPYTHONASYNCIODEBUG
задана непустая строка, в противном случае —False
.
-
loop.
set_debug
(enabled: bool)¶ Установить режим отладки цикла событий.
Изменено в версии 3.7: Новый параметр командной строки
-X dev
теперь также можно использовать для включения режима отладки.
См.также
Запущенные подпроцессы¶
Методы, описанные в этом подразделе, относятся к низкоуровневым. В обычном
коде async/await рассмотрите возможность использования вместо них
вспомогательных функций высокого уровня asyncio.create_subprocess_shell()
и asyncio.create_subprocess_exec()
.
Примечание
Цикл событий asyncio в Windows по умолчанию не поддерживает подпроцессы. Подробнее см. Поддержка подпроцессов в Windows.
-
coroutine
loop.
subprocess_exec
(protocol_factory, *args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)¶ Создать подпроцесс из одного или нескольких строковых аргументов, указанных в args.
args должен быть списком строк, представленных:
str
;- или
bytes
, закодированный как кодировка файловой системы.
Первая строка указывает исполняемый файл программы, а остальные строки указывают аргументы. Вместе строковые аргументы образуют
argv
программы.Это похоже на класс стандартной библиотеки
subprocess.Popen
, вызываемый сshell=False
и списком строк, переданных в качестве первого аргумента; однако, еслиPopen
принимает единственный аргумент, который является списком строк, subprocess_exec принимает несколько строковых аргументов.protocol_factory должен быть вызываемым, возвращающим подкласс класса
asyncio.SubprocessProtocol
.Прочие параметры:
stdin может быть любым из них:
- файловый объект, представляющий канал, который должен быть подключён к
стандартному входному потоку подпроцесса с использованием
connect_write_pipe()
- константа
subprocess.PIPE
(по умолчанию), которая создаст новый канал и соединит его - значение
None
, которое заставит подпроцесс наследовать файловый дескриптор этого процесса - константа
subprocess.DEVNULL
, которая указывает, что будет использоваться специальный файлos.devnull
- файловый объект, представляющий канал, который должен быть подключён к
стандартному входному потоку подпроцесса с использованием
stdout может быть любым из них:
- файловый объект, представляющий канал, который должен быть подключён к
стандартному потоку вывода подпроцесса с использованием
connect_write_pipe()
- константа
subprocess.PIPE
(по умолчанию), которая создаст новый канал и соединит его - значение
None
, которое заставит подпроцесс наследовать дескриптор файла от этого процесса - константа
subprocess.DEVNULL
, которая указывает, что будет использоваться специальный файлos.devnull
- файловый объект, представляющий канал, который должен быть подключён к
стандартному потоку вывода подпроцесса с использованием
stderr может быть любым из них:
- файловый объект, представляющий канал, который должен быть подключён к
стандартному потоку ошибок подпроцесса с использованием
connect_write_pipe()
- константа
subprocess.PIPE
(по умолчанию), которая создаст новый канал и соединит его - значение
None
, которое заставит подпроцесс наследовать файловый дескриптор этого процесса - константа
subprocess.DEVNULL
, которая указывает, что будет использоваться специальный файлos.devnull
- константа
subprocess.STDOUT
, которая соединит стандартный поток ошибок со стандартным потоком вывода процесса
- файловый объект, представляющий канал, который должен быть подключён к
стандартному потоку ошибок подпроцесса с использованием
Все остальные ключевые аргументы передаются в
subprocess.Popen
без интерпретации, за исключением bufsize, universal_newlines, shell, text, encoding и errors которые вообще не должны указываться.API подпроцесса
asyncio
не поддерживает декодирование потоков как текста.bytes.decode()
можно использовать для преобразования байтов, возвращаемых из потока, в текст.
См. конструктор класса
subprocess.Popen
для документации по другим аргументам.Возвращает пару
(transport, protocol)
, где transport соответствует базовому классуasyncio.SubprocessTransport
, а protocol — объект, экземпляр которого был создан с помощью protocol_factory.
-
coroutine
loop.
subprocess_shell
(protocol_factory, cmd, *, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)¶ Создать подпроцесс из cmd, который может быть строкой
str
илиbytes
, закодированной в кодировке файловой системы, используя синтаксис «оболочки» платформы.Это похоже на класс стандартной библиотеки
subprocess.Popen
, который называетсяshell=True
.protocol_factory должен быть вызываемым, возвращающим подкласс класса
SubprocessProtocol
.См.
subprocess_exec()
для получения дополнительных сведений об оставшихся аргументах.Возвращает пару
(transport, protocol)
, где transport соответствует базовому классуSubprocessTransport
, а protocol — это объект, созданный с помощью protocol_factory.
Примечание
Приложение несёт ответственность за то, чтобы все пробелы и специальные
символы были указаны в кавычках, чтобы избежать уязвимостей инъекция оболочки. Функцию
shlex.quote()
можно использовать для правильного экранирования
пробелов и специальных символов в строках, которые будут использоваться для
создания команд оболочки.
Дескрипторы обратного вызова¶
-
class
asyncio.
Handle
¶ Объект-оболочка обратного вызова, возвращаемый
loop.call_soon()
,loop.call_soon_threadsafe()
.-
cancel
()¶ Отменить обратный вызов. Если обратный вызов уже был отменён или выполнен, метод не действует.
-
cancelled
()¶ Вернуть
True
, если обратный вызов был отменен.Добавлено в версии 3.7.
-
-
class
asyncio.
TimerHandle
¶ Объект-оболочка обратного вызова, возвращаемый
loop.call_later()
иloop.call_at()
.Класс является подклассом
Handle
.-
when
()¶ Вернуть запланированное время обратного вызова как
float
секунд.Время — это абсолютная временная метка с той же привязкой ко времени, что и
loop.time()
.Добавлено в версии 3.7.
-
Server объекты¶
Server объекты создаются функциями loop.create_server()
,
loop.create_unix_server()
, start_server()
и
start_unix_server()
.
Не создавайте экземпляр класса напрямую.
-
class
asyncio.
Server
¶ Объекты Server — асинхронные менеджеры контекста. При использовании в операторе
async with
гарантируется, что объект Server закрыт и не принимает новые соединения после завершения оператораasync with
:srv = await loop.create_server(...) async with srv: # некоторый код # В данный момент srv закрыт и больше не принимает новые подключения.
Изменено в версии 3.7: Объект Server — это асинхронный менеджер контекста, начиная с Python 3.7.
-
close
()¶ Прекращение обслуживания: закрыть прослушивающие сокеты и установить для атрибута
sockets
значениеNone
.Сокеты, которые представляют существующие входящие клиентские соединения, остаются открытыми.
Сервер закрывается асинхронно, использовать корутину
wait_closed()
, чтобы дождаться закрытия сервера.
-
get_loop
()¶ Вернуть цикл событий, связанный с серверным объектом.
Добавлено в версии 3.7.
-
coroutine
start_serving
()¶ Начать принимать подключения.
Метод идемпотентен, поэтому его можно вызывать, когда сервер уже обслуживает.
start_serving только для ключевого параметра для
loop.create_server()
иasyncio.start_server()
позволяет создать объект Server, который изначально не принимает соединения. В этом случае можно использоватьServer.start_serving()
илиServer.serve_forever()
, чтобы сервер начал принимать соединения.Добавлено в версии 3.7.
-
coroutine
serve_forever
()¶ Начать принимать соединения, пока корутина не будет отменена. Отмена задачи
serve_forever
приводит к закрытию сервера.Метод можно вызвать, если сервер уже принимает соединения. На один объект Server может существовать только одна задача
serve_forever
.Пример:
async def client_connected(reader, writer): # Взаимодействие клиента с потоками # читения/записи. Например: await reader.readline() async def main(host, port): srv = await asyncio.start_server( client_connected, host, port) await srv.serve_forever() asyncio.run(main('127.0.0.1', 0))
Добавлено в версии 3.7.
-
is_serving
()¶ Вернуть
True
, если сервер принимает новые соединения.Добавлено в версии 3.7.
-
sockets
¶ Список объектов
socket.socket
, которые прослушивает сервер.Изменено в версии 3.7: До Python 3.7
Server.sockets
использовался для непосредственного возврата внутреннего списка серверных сокетов. В 3.7 возвращается копия этого списка.
-
Реализации цикла событий¶
asyncio поставляется с двумя разными реализациями цикла событий:
SelectorEventLoop
и ProactorEventLoop
.
По умолчанию asyncio настроен на использование SelectorEventLoop
в
Unix и ProactorEventLoop
в Windows.
-
class
asyncio.
SelectorEventLoop
¶ Цикл событий на основе модуля
selectors
.Использует наиболее эффективный селектор, доступный для данной платформы. Также можно вручную настроить точную реализацию селектора, который будет использоваться:
import asyncio import selectors selector = selectors.SelectSelector() loop = asyncio.SelectorEventLoop(selector) asyncio.set_event_loop(loop)
Доступность: Unix, Windows.
-
class
asyncio.
ProactorEventLoop
¶ Цикл событий для Windows, использующий «порты завершения ввода-вывода» (IOCP).
Доступность: Windows.
-
class
asyncio.
AbstractEventLoop
¶ Абстрактный базовый класс для асинхронных циклов событий.
В разделе Методы цикла событий перечислены все методы, которые должна была определять альтернативная реализация
AbstractEventLoop
.
Примеры¶
Обратите внимание, что все примеры в этом разделе целенаправленно рассказывают,
как использовать API-интерфейсы низкоуровневого цикла событий, такие как
loop.run_forever()
и loop.call_soon()
. Современные приложения
asyncio редко нужно писать таким образом; рассмотрите возможность использования
функций высокого уровня, таких как asyncio.run()
.
«Привет, мир» с call_soon()¶
Пример использования метода loop.call_soon()
для планирования обратного
вызова. Обратный вызов отображает "Hello World"
, а затем останавливает цикл
событий:
import asyncio
def hello_world(loop):
"""Коллбэк печатает 'Hello World' и останавливает событийный цикл"""
print('Hello World')
loop.stop()
loop = asyncio.get_event_loop()
# Запланировать вызов hello_world()
loop.call_soon(hello_world, loop)
# Блокирующий вызов прерываемый loop.stop()
try:
loop.run_forever()
finally:
loop.close()
См.также
Аналогичный пример Hello World, созданный с помощью
корутины и функции run()
.
Отобразите текущую дату с помощью call_later()¶
Пример обратного вызова, отображающего текущую дату каждую секунду. Обратный
вызов использует метод loop.call_later()
для перепланирования себя через
5 секунд, а затем останавливает цикл событий:
import asyncio
import datetime
def display_date(end_time, loop):
print(datetime.datetime.now())
if (loop.time() + 1.0) < end_time:
loop.call_later(1, display_date, end_time, loop)
else:
loop.stop()
loop = asyncio.get_event_loop()
# Запланировать первый вызов display_date()
end_time = loop.time() + 5.0
loop.call_soon(display_date, end_time, loop)
# Блокировка вызова прервана loop.stop()
try:
loop.run_forever()
finally:
loop.close()
См.также
Аналогичный пример текущей даты, созданный с
помощью корутины и функции run()
.
Следите за файловым дескриптором на предмет событий чтения¶
Подождать, пока файловый дескриптор получит некоторые данные с помощью метода
loop.add_reader()
, а затем закрыть цикл обработки событий:
import asyncio
from socket import socketpair
# Создаёт пару связанных файловых дескрипторов
rsock, wsock = socketpair()
loop = asyncio.get_event_loop()
def reader():
data = rsock.recv(100)
print("Received:", data.decode())
# Мы закончили: отменить регистрацию файлового дескриптора
loop.remove_reader(rsock)
# Отстановить событийный цикл
loop.stop()
# Зарегистрировать дескриптор файла для события чтения
loop.add_reader(rsock, reader)
# Имитировать приём данных из сети
loop.call_soon(wsock.send, 'abc'.encode())
try:
# Запуск событийного цикла
loop.run_forever()
finally:
# Завершаем работу. Закрыть сокеты и цикл обработки событий.
rsock.close()
wsock.close()
loop.close()
См.также
- Аналогичный пример с
использованием транспортов, протоколов и метода
loop.create_connection()
. - Другой аналогичный
пример,
использующий высокоуровневую функцию и потоки
asyncio.open_connection()
.
Установить обработчики сигналов для SIGINT и SIGTERM¶
(Пример signals
работает только в Unix.)
Зарегистрировать обработчики сигналов SIGINT
и SIGTERM
с
помощью метода loop.add_signal_handler()
:
import asyncio
import functools
import os
import signal
def ask_exit(signame, loop):
print("got signal %s: exit" % signame)
loop.stop()
async def main():
loop = asyncio.get_running_loop()
for signame in {'SIGINT', 'SIGTERM'}:
loop.add_signal_handler(
getattr(signal, signame),
functools.partial(ask_exit, signame, loop))
await asyncio.sleep(3600)
print("Event loop running for 1 hour, press Ctrl+C to interrupt.")
print(f"pid {os.getpid()}: send SIGINT or SIGTERM to exit.")
asyncio.run(main())