Я пишу модуль 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)
Для этого нужно, чтобы ваш класс планировал задачу в цикле событий, используя 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__
для отмены всех обновлений.
Общий поток программы 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-запроса.
Если вы дадите более подробную информацию о протоколе, который вы используете, вы, вероятно, получите более конкретные рекомендации по вашей проблеме. Надеюсь, что это общее резюме полезно.