asyncio - Как можно использовать сопрограммы в обработчиках сигналов?

Я разрабатываю приложение, которое использует asyncio из python3.4 для работы в сети. Когда это приложение корректно завершает работу, узел должен «отключиться» от концентратора. Это отключение является активным процессом, для которого требуется подключение к сети, поэтому цикл должен дождаться его завершения, прежде чем завершить работу.

Моя проблема в том, что использование сопрограммы в качестве обработчика сигнала приведет к тому, что приложение не будет закрыто. Пожалуйста, рассмотрите следующий пример:

import asyncio
import functools
import os
import signal

@asyncio.coroutine
def ask_exit(signame):
    print("got signal %s: exit" % signame)
    yield from asyncio.sleep(10.0)
    loop.stop()

loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame),
                                        functools.partial(ask_exit, signame))

print("Event loop running forever, press CTRL+c to interrupt.")
print("pid %s: send SIGINT or SIGTERM to exit." % os.getpid())
loop.run_forever()

Если вы запустите этот пример, а затем нажмете Ctrl+C, ничего не произойдет. Вопрос в том, как мне добиться такого поведения с сигналами и сопрограммами?


person Jinnog    schedule 26.04.2014    source источник
comment
Я нашел ответ на этот вопрос в списке python-tulip: groups.google.com/d/msg/python-tulip/Jce-VVXJZvk/o-g74rtuIHkJ   -  person Jinnog    schedule 27.04.2014
comment
если вы считаете, что нашли решение (запланируйте сопрограмму на сигнал, используя asyncio.async вместо того, чтобы напрямую вызывать ее как обработчик сигнала); вы можете опубликовать его как свой собственный ответ и принять его, чтобы другие могли видеть, что на вопрос дан ответ. Несвязанный: вы можете оскорбить некоторых волонтеров на этом сайте, предполагая, что мотивом для их помощи вам являются вымышленные точки доступа в Интернет.   -  person jfs    schedule 04.05.2014


Ответы (3)


Синтаксис для python >=3.5

loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame),
                            lambda: asyncio.ensure_future(ask_exit(signame)))
person svs    schedule 10.02.2017
comment
Это не работает должным образом. Обе лямбда-выражения захватывают переменную цикла signame по ссылке, и обе лямбда-выражения будут вызываться, как если бы было отправлено SIGTERM (вызывается ask_exit('SIGTERM')). Написание для каждой сигнальной линии: loop.add_signal_handler(signal.SIGINT, lambda: asyncio.ensure_future(ask_exit('SIGINT'))) - самое простое решение - person R2RT; 26.05.2019
comment
Оно работает. Просто совет Внутри ask_exit вы могли остановить цикл, но не смогли закрыть его - person ontananza; 09.12.2019

loop = asyncio.get_event_loop()
for signame in ('SIGINT', 'SIGTERM'):
    loop.add_signal_handler(getattr(signal, signame),
                            asyncio.async, ask_exit(signame))

Таким образом, сигнал заставляет ваш ask_exit планироваться в задаче.

person Jason Fried    schedule 07.08.2014
comment
Это недопустимый синтаксис в python 3.5.1. - person Jared Mackey; 17.08.2016
comment
ask_exit никогда не было ожидаемым исключением в python 3.6.4 - person jerrypy; 15.01.2018

питон3.8

  • 1-я попытка: использовал async def handler_shutdown и завернул его в loop.create_task() при переходе к add_signal_handler()
  • 2-я попытка: не используйте асинхронность для def handler_shutdown().
  • 3-я попытка: обернуть handler_shutdown и param в functools.partial()

e.g.

import asyncio
import functools
def handler_shutdown(signal, loop, tasks, http_runner, ):
    ...
    ...
def main():
    loop = asyncio.get_event_loop()
    for signame in ('SIGINT', 'SIGTERM', 'SIGQUIT'):
                print(f"add signal handler {signame} ...")
                loop.add_signal_handler(
                    getattr(signal, signame),
                    functools.partial(handler_shutdown,
                            signal=signame, loop=loop, tasks=tasks,
                            http_runner=http_runner
                            )
                    )
  • Основная проблема, с которой я столкнулся, была ошибка

    raise TypeError("coroutines cannot be used "

  • решил это, обернув процедуру в loop.create_task()

  • затем решил это, удалив функцию обработчика сигнала асинхронной формы
  • для именованного параметра обработчика также используйте functools.partial
person Pieter    schedule 15.02.2020