Комментарии 10
Я не понял, а где timeout в requests в завершающем примере. Нигде? Тогда: If no timeout is specified explicitly, requests do not time out. И уже не важно какой там токен. Сидим ждём. Ну и пока сидим и ждём, переписываем с реализацией настоящего прекращения по timeout.
После того, как перепишем, проверяем реализацию. Поскольку параметр timeout в requests не учитывает время после начала передачи данных, и есть риск опять сидеть и ждать.
Правильно я понимаю, что с multiprocessing
работать не будет?
Верно.
Видимо, если вы в другой процесс передадите токен, то его не удастся отменить, но по таймауту-то отмена сработает?
Было бы удобно завести какой-нибудь токен для asyncio и его друзей.
Например, создаем токен и делаем await на него, после cancel он возвращает управление. Сейчас для таких целей использую asyncio.Event, но ваши токены выглядят удобнее.
В первую очередь хочется избавиться от конструкций вроде таких:
await asyncio.wait([e.wait() for e in stop_events], timeout=10, return_when=asyncio.FIRST_COMPLETED)
Да, это хорошая идея, я добавил такую возможность для всех токенов, можете попробовать.
Спасибо, но у меня есть несколько замечаний.
Во-первых, мне кажется, что введение метода wait
только для асинхронных операций - не очень красиво. И не очень очевидно. Я бы предложил реализовать эту фичу и для синхронного режима тоже. Варианты: методы wait/wait_async, что-то вроде аналога asgiref.sync.sync_to_async или вообще декоратор.
Кстати, вместо `await token.wait()` может быть сделать просто `await token` ?
Во-вторых, ваша реализация с помощью while/sleep(0.0001) мне кажется очень неправильной. Смысл asyncio в том чтобы освобождать процессор, а вы заставляете его переключаться в вашу проверку и заново проверять все условия (втч синхронные) - каждый цикл эвентлупа. Простите, но уж лучше я останусь со своим вариантом. Должен сказать, что не могу сразу предложить вариант лучше. Возможно имеет смысл ввести отдельный асинхронный токен. И, возможно, не разрешать его смешивать с синхронными (по крайней мере поначалу). Поскольку, например ваша run_function
теоретически тоже может пригодиться быть асинхронной.
Кстати, ваш CounterToken
в этом методе лучше вообще принудительно выключать.
Замечание по документации, на которую вы дали ссылку - вы же в курсе что у вас код синхронный в примере? Несмотря на await, все строки кода будут выполняться последовательно. Метод asyncio.create_task
подошел бы лучше для вызова do_something
.
Безо всей этой асинхронной ерунды ваша реализация выглядит красиво. Не рассматривайте мои предложения как требования - может быть и оставить всё это синхронным..
Тут много хороших идей, часть из них я реализовал, а часть не буду.
Метод wait
начиная с версии 0.0.11 является универсальным. По умолчанию он полностью синхронный, но если туда передать опциональным аргументом is_async=True
, то метод можно эвейтить. await token
выглядит красиво, но пока не реализовано. Возможно, когда-нибудь в будущем это случится, и я готов принимать PRы.
По поводу sleep
. В действительности все проверки не прогоняются каждый цикл ивентлупа. Они прогоняются раз в промежуток времени, указанный пользователем как step
. По умолчанию это действительно 0.0001
секунды в текущей реализации, однако его можно изменить, если кажется нужным. Чем реже проверки будут происходить - тем "дешевле" это будет для процессора, но тем сильнее можно "переспать" момент отмены.
Само по себе ожидание через sleep
или что-то похожее (в нашем случае для асинхронного варианта используется asyncio.sleep), к сожалению, неизбежно, если мы хотим сделать универсальную библиотеку, а не заточенную конкретно под async. Если пойти по пути полной оптимизации под async, то для обычного питона она, к сожалению, станет работать хуже, то есть либо медленнее, либо с более распухшим API, дублирующим основные функции. Дело в том, что существует довольно много причин, по которым токен может быть отменен. Это может быть выполнение произвольного условия, вызов у токена метода cancel()
, а также наступление любого из этих событий в любом из вложенных токенов. Некоторые кейсы, например вызов cancel()
, действительно можно было бы более эффективно с асинхронной точки зрения обработать, засунув внутрь метода, скажем, вызов коллбека, который отменит какой-то из ожидабельных примитивов asyncio. Однако в этом случае библиотека для обычного, не асинхронного кода, начнет работать медленнее, а именно обычному коду я предпочитаю отдавать приоритет. Я практически не вижу способов сделать это красиво, не отъедая перфоманс у обычного (не async) варианта использования и не переусложняя код библиотеки.
По поводу автоотмены CounterToken
, в нем уже по умолчанию была защита от скручивания теми запросами, которые он делает себе сам. Вот пример кода:
from cantok import CounterToken, TimeoutToken
counter_token = CounterToken(5)
timeout_token = TimeoutToken(5)
(counter_token + timeout_token).wait()
print(repr(counter_token))
# CounterToken(5, direct=True) - счетчик не скручен.
В статье я посчитал такие подробности излишними.
По поводу синхронности примера кода, готов принимать PRы с более красивыми примерами, но вообще у меня стояла цель просто показать, что что-то можно эвейтить, более сложная реализация в примере кода, на мой взгляд, обычно излишня.
Многопоточный Python на примерах: токены отмены