Как получить несколько историй каналов одновременно?

#python #python-3.x #concurrency #discord #discord.py

Вопрос:

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

 async def starthistory(self, ctx, key, msg, num):  for channel in ctx.guild.text_channels:  async for message in channel.history(limit=1):  message_content = message.content.lower()  if len(message.embeds) gt; 0:  if len(message.embeds[0].title) gt; 0:  message_content = message.embeds[0].title.lower()  elif len(message.embeds[0].author) gt; 0:  message_content = message.embeds[0].author.lower()  elif len(message.embeds[0].description) gt; 0:  message_content = message.embeds[0].description.lower()    if message_content.startswith(key.lower()):  num  = 1  msg  = f"n**{num}.** {channel.mention} - **{channel.name}**"    #startswith  @_list.command(name="starts_with",  aliases=["startswith", "sw", "s"],  brief="Lists all channels with message starting with lt;keygt;.",  help="Lists all channels with last message starting with the word/phrase lt;keygt;.",  case_insensitive=True)  async def _starts_with(self, ctx, *, key):    msg = f"Channels with last message starting with `{key}`:"  num = 0  wait = await ctx.send(f"Looking for messages starting with `{key}`...")   asyncio.create_task(self.starthistory(ctx=ctx, key=key, msg=msg, num=num))    if num == 0:  msg  = "n**None**"  msg  = f"nnTotal number of channels = **{num}**"  for para in textwrap.wrap(msg, 2000, expand_tabs=False, replace_whitespace=False, fix_sentence_endings=False, break_long_words=False, drop_whitespace=False, break_on_hyphens=False, max_lines=None):  await ctx.send(para)  await asyncio.sleep(0.5)  await wait.edit(content="✅ Done.")  

Я хочу, чтобы он одновременно просматривал историю каждого канала, чтобы это не заняло так много времени. В настоящее время мой код не изменяет уже определенные переменные: num всегда 0 и msg всегда None .

Как одновременно просматривать историю каждого канала, а не по одному за раз?

Комментарии:

1. Не могли бы вы объяснить, что вы подразумеваете под «это, очевидно, не работает»? Вы получили и ошиблись? Был ли результат не таким, как вы ожидали?

2. ах, ну, я установил 2 переменные, затем создал задачу для запуска функции starthistory, но она не изменяет уже определенные переменные, поэтому «num»всегда равно 0, а» msg «всегда » Нет».

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

4. Ах, я вижу, спасибо за разъяснение, вы пытаетесь использовать create_task для достижения параллелизма, но это не работает для вас. Предполагая, что ваш код работает без вашей попытки параллелизма, я напишу ответ на этот вопрос.

Ответ №1:

asyncio.create_task(coro) создает асинхронную задачу и запускает ее в фоновом режиме. Чтобы позволить вашему for циклу работать асинхронно, когда все текстовые каналы обрабатываются одновременно, вы должны использовать asyncio.gather(coros) вместо этого.

Вот рабочий код (я сократил ваш код только до соответствующих частей):

 @staticmethod async def check_history(msgs, channel, key, semaphore):  async with semaphore:  async for message in channel.history(limit=1):  message_content = message.content.lower()  # trimmed some code...  if message_content.startswith(key.lower()):  num = len(msgs)  msgs  = [f"**{num}.** {channel.mention} - **{channel.name}**"]   @_list.command() async def _starts_with(self, ctx, *, key):  msgs = [f"Channels with last message starting with `{key}`:"]  tasks = []  semaphore = asyncio.Semaphore(10)   for channel in ctx.guild.text_channels:  tasks  = [self.check_history(msgs, channel, key, semaphore)]   await asyncio.gather(*tasks)   if len(msgs) == 1:  msgs  = ["**None**"]  msgs  = [f"nTotal number of channels = **{len(msgs)}**"]  msg = "n".join(msgs)  print(msg)  

Основные замечания/почему это работает:

  • Раньше я asyncio.gather() ждал всех check_history сопрограмм.
  • Я использовал a asyncio.Semaphore(10) , который ограничит максимальный параллелизм до 10. API Discord не любит, когда вы отправляете слишком много запросов одновременно. При слишком большом количестве каналов вы можете быть временно заблокированы.
  • Обычно не рекомендуется передавать неизменяемые объекты (strs и int) внешней функции и пытаться изменить ее значения. Для вашего варианта использования я считаю, что лучшей альтернативой является использование списка msg, а затем str.join() его позже. От этого тоже можно избавиться num .