Email or username:

Password:

Forgot your password?
Top-level
Ambassador Tablicek

Чот не понимаю я ничего в #asyncio в #python, похоже. Единственный способ нормально завершать несколько запущенных asyncio.Task с while True внутри и не сыпать кучей трейсбэков при KeyboardInterrupt оказался:

asyncio.run(func)

def func():
...
await asyncio.wait([
asyncio.create_task(task1()),
asyncio.create_task(task2())
])

Любые другие приводили либо к мусору, либо к бесконечному повисанию, я явно что-то делал не так.

Что пробовал:

1. Gather

def func():
...
await asyncio.gather(
asyncio.create_task(task1()),
asyncio.create_task(task2())
)

2. Обработку сигналов и ручная отмена тасок

loop.add_signal_handler(sigint, cancel_tasks, [t1, t2]))

def cancel_all(tasks: list[Task]):
for task in tasks:
if task != asyncio.current_task() and not task.done():
task.cancel()

3. Где-то посреди этого было явное создание эвент-лупа и попытки его останавливать, что-то типа:

if __name__ == '__main__':
loop = asyncio.new_event_loop()
loop.run_until_complete(func())
try:
loop.run_forever()
except KeyboardInterrupt:
loop.stop()

4. В том числе без каких-либо зацепок на событие, то есть таски просто создавались и.. ну сдохнут и сдохнут:

def func():
...
asyncio.create_task(task1())
asyncio.create_task(task2())

Конкретно ругань уже не помню, а откатываться к падающему состоянию не хочу, но что-то про то что task was destroyed but it was pending. Типа надо дождаться пока они на следующих тиках эвентлупа нормально отработают asyncio.CancelledError и только после этого уже loop стопать. Но мне чот не повезло и дебаггером побегать по коду обработки сигнала не вышло, он тупо завершался, вот я хер и забил.

Теперь чувствую себя джуном :c

3 comments
Rubikoid

@strizhechenko «просто создавать таски» из 4 пункта нельзя.
За ними может прийти GC и они испарятся.
Что у тебя и происходит, вроде как.

Вариант с gather наиболее логичный и он должен работать.
Проверь параметры gather’а, он умеет обрабатывать исключения.

Если совсем правильно делать, конечно, то надо в каждой таске ловить и обрабатывать cancelerror, ну и кэнселить их.

Плюс у тебя await’ы в некоторых местах в синхронных функах, это точно то?)

@strizhechenko «просто создавать таски» из 4 пункта нельзя.
За ними может прийти GC и они испарятся.
Что у тебя и происходит, вроде как.

Вариант с gather наиболее логичный и он должен работать.
Проверь параметры gather’а, он умеет обрабатывать исключения.

Если совсем правильно делать, конечно, то надо в каждой таске ловить и обрабатывать cancelerror, ну и кэнселить их.

[DATA EXPUNGED]
Rubikoid

@strizhechenko доехал домой, добрался до репла питона, проверил.

В общем случае, SIGINT у тебя вызывает KeyboardException в текущей исполняющейся строке питона.

Для asyncio есть несколько вариантов развития событий, в зависимости от того, чем занимается луп прямо сейчас.

Вариант 1: луп исполняет какой-то синхронный код. Тогда это исключение вылетит на этот синхронный код, провалится наверх до gather'а, и дальше в зависимости от параметров gather'а будет обрабатываться.

Вариант 2: луп что-то ждёт. В этом случае оно прилетит в сам луп, и луп тебе отменит все корутины, какие найдет (в том числе все таски твои).
В итоге await gather кинет исключение и оно поедет наверх.

Вот на этом можно поиграться: yaso.su/dAlePnTi

Учитывай, что CancelledError наследуется от BaseException, и через обычный except Exception его не поймать.

@strizhechenko доехал домой, добрался до репла питона, проверил.

В общем случае, SIGINT у тебя вызывает KeyboardException в текущей исполняющейся строке питона.

Для asyncio есть несколько вариантов развития событий, в зависимости от того, чем занимается луп прямо сейчас.

Вариант 1: луп исполняет какой-то синхронный код. Тогда это исключение вылетит на этот синхронный код, провалится наверх до gather'а, и дальше в зависимости от параметров gather'а будет обрабатываться.

Ambassador Tablicek

Научил наконец демонюгу следить за подписками, инициировать из них свой стейт и отправлять команды только по необходимости. Осталось придумать как это добро отличать от ручного переключения (скорее всего достаточно запомнить время подписки на топик) и что и насколько блокировать. В теории, если я кнопками на приборе поменял скорость в 22:35, на расписание выключения в 23:00 оно влиять не должно. А вот скорость лучше сохранять.. некоторое время. Пока склоняюсь к четырём часам.

Переключился на протокол v5, судя по wireshark он умеет группировать пакеты в один, типа подтвердить подписку + отправить текущее значение топика. Но делает это рандомно. Но вроде работает.

Научил этого демонюгу управлять светильниками в кабинете. Светильники в MQTT не умеют, но умеют в JSON (ну почти RPC) over TCP. Базово разобрался с вкл/выкл, но вроде можно и цвет + температуру настроить, вот тогда заживём (в смысле можно будет выход в инет им заблочить уже наконец), до этого после отрубания у них происходил сброс настроек до какого-то раздражающего красного цвета.

Нашёл наконец где вебка у рекуператора в детской. Как оказалось, я перепутал его с водонагревателем, когда создавал статические записи в DHCP. А вот с водонагревателем сложно. Я обновил его до 1.25 по глупости и теперь способ его хака с подсовыванием своего брокера с помощью DNS-hijacking не прокатывает. Клятi rusclimat =/

Научил наконец демонюгу следить за подписками, инициировать из них свой стейт и отправлять команды только по необходимости. Осталось придумать как это добро отличать от ручного переключения (скорее всего достаточно запомнить время подписки на топик) и что и насколько блокировать. В теории, если я кнопками на приборе поменял скорость в 22:35, на расписание выключения в 23:00 оно влиять не должно. А вот скорость лучше сохранять.. некоторое время. Пока склоняюсь к четырём часам.

Go Up