Как настроить фоновый процесс, используя asyncio как часть модуля

1

Я пишу модуль python (экспериментальный пример использования), который дает пользователям возможность отправлять сообщения на другой компьютер. Компьютер, на который мы хотим отправить сообщение, имеет неизвестный адрес, который изменяется в произвольные моменты времени. Существует еще один компьютер (посредник), который предоставляет адрес и время до истечения срока действия адреса.

Для простоты позвоните, позвонив тем, кто отправляет сообщение компьютер-A, тот, кто получает компьютер-B, а компьютер-C является посредником, с которым нам нужно связаться для адреса компьютера-B.

То, что я пытаюсь выполнить:

Я хочу иметь возможность отложить процесс ожидания истечения срока действия asyncio.sleep(). Когда время истечет, я ожидаю, что процесс вернет управление циклом событий и запустит функцию для обновления адреса.

Проблема, с которой я сталкиваюсь, заключается в том, как я реализую это в классе, где я не могу вызвать run_until_complete/run_forever (или я явно неправ). Как вы реализуете такую вещь, используя инфраструктуру asyncio?

Пример гипотетического кода:

from some_random_messaging_service import deliver_msg

INTERMEDIARY_COMPUTER_C_ADDRESS = "some.random.address"

class CustomMessagingSystem:

    def __init__(self, computer=None):
        """Constructor

        :param computer: computer name

        """
        self.addresses = {}
        if computer:
            self.get_address(computer)

    def get_address(self, computer):
        """Gets address from computer-C (intermediary)

        :param computer: computer name

        """
        self.addresses[computer] = self.find_address(INTERMEDIARY_COMPUTER_C_ADDRESS, computer)
        await self.update_address(computer, self.expiration_time(computer))

    def expiration_time(self, computer):
        """
        :param computer: computer name
        :return: address expiration time in seconds
        """
        return self.addresses[computer][1]

    def address(self, computer):
        """

        :param computer: computer name
        :return: computer address

        """
        return self.addresses[computer][0]

    async def update_address(self, computer, expiration_time):
        """updates address of computer after given expiration_time

        :param computer: computer name
        :expiration_time: expiration time in seconds

        """
        asyncio.sleep(expiration_time)
        self.get_address(computer)

    def send_message(self, computer, message):
        """Sends message to target computer

        :param computer: computer name
        :param message: UTF-8 message

        """
        deliver_msg(self.address(computer), message)
Теги:
python-3.x
async-await
asynchronous
python-asyncio

2 ответа

1
Лучший ответ

Для этого нужно, чтобы ваш класс планировал задачу в цикле событий, используя create_task. Это можно сделать до или после запуска цикла событий.

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

class CustomMessagingSystem:

    def __init__(self, computer=None, ioloop=asyncio.get_event_loop()):
        self.addresses = {}
        self.updaters = {}
        self.ioloop = ioloop
        if computer:
            self._add_address(computer)

    def get_address(self, computer):
        try:
            return self.addresses[computer]
        except KeyError:
            self._add_address(computer)
            return self.addresses[computer]

    def _add_address(self, computer):
            address = self.find_address(INTERMEDIARY_COMPUTER_C_ADDRESS, computer)
            task = self.ioloop.create_task(self._update_address, computer)
            self.updaters[computer] = task
            self.addresses[computer] = address

    async def _update_address(self, computer):
        while True:
            addr, expiration_time = self.find_address(INTERMEDIARY_COMPUTER_C_ADDRESS, computer)
            self.addresses[addr] = addr
            await asyncio.sleep(expiration_time)

    def send_message(self, computer, message):
        deliver_msg(self.get_address(computer), message)

Естественно, если цикл событий никогда не запускается, обновление никогда не произойдет.

Наконец, что-то, что вы хотите сделать, - это контролировать время жизни этих задач обновления. Я не реализовал это в приведенном выше примере, чтобы не допустить его. Стандартный подход заключается в том, чтобы сделать ваш класс менеджером контекста и получить __exit__ для отмены всех обновлений.

  • 0
    Это близко к тому, что я описывал - на ваш взгляд, Джо, я полагаю, что пользователь этого кода должен запустить цикл обработки событий? Я должен посмотреть на несколько модулей поддержки Asyncio, чтобы увидеть, как они реализовали это
  • 0
    Да, если вы хотите использовать asyncio, клиентская программа всегда должна будет «сотрудничать» с вами, используя саму asyncio. Если вы не хотите этого ограничения, тогда другим вариантом будет использование потоков, но тогда вы попадете в совершенно другой мир боли.
0

Общий поток программы asyncio - это нечто подобное.

async def long_io_bound_operation():
    await <call some async function that does your work>
    ...

def main():
    asyncio.get_event_loop().run_until_complete(long_io_bound_operation())

Есть другие способы, чтобы на самом деле ждать на сопрограмме, возвращаемой вызовом функции long_io_bound_operation() зависимости от того, что вы хотите, но это основная его форма. Прочитайте модуль asyncio для подробных подробностей, но суть в том, что каждый раз, когда вы используете ключевое слово await, среда выполнения python может выбрать неблокируемое ожидание результата, а не блокирование и прядение в ожидании некоторой работы что нужно сделать.

Мне немного непонятно, из вашего кода, какой протокол вы планируете вызывать для этого сообщения, но это действительно хорошая ставка, что уже существует асинхронно-совместимая оболочка вокруг этого протокола, написанного для вас. aiohttp представляет собой асинхронную оболочку для HTTP-запроса.

Если вы дадите более подробную информацию о протоколе, который вы используете, вы, вероятно, получите более конкретные рекомендации по вашей проблеме. Надеюсь, что это общее резюме полезно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню