Как мне ожидать корутину Timer, которая требует быть асинхронной в discord.py?

Вопрос или проблема

Я пытаюсь создать простой бот, который может отправлять эхо-сообщение, которое вы отправили в личных сообщениях, через определенное время. Вот команда и функция, которую она вызывает внутри таймера:

@bot.command()
async def reminder(ctx: commands.Context, message: str) -> None:
    print("планирование напоминания")
    event = {
        "message": message,
        "timer": Timer(5.0, send_rem, args=[ctx.author, message])
    }

    user_events[str(ctx.author.id)] = event
    event["timer"].start()
    print("планирование напоминания")

async def send_rem(author, message):
    print("отправка напоминания")
    DM_CHNL = await author.create_dm() if (author.dm_channel is None) else author.dm_channel
    if DM_CHNL != None:
        await DM_CHNL.send(message)

Тем не менее, когда я запускаю бота и использую команду напоминания, я получаю эту ошибку после истечения времени таймера:

C:\Users\xxxxx\AppData\Local\Programs\Python\Python313\Lib\threading.py:1342: RuntimeWarning: coroutine 'send_rem' was never awaited
  self.function(*self.args, **self.kwargs)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

Я предполагаю, что проблема в том, что функция send_rem асинхронная, но я не могу остановить это, так как создание и отправка сообщений оба являются асинхронными, и я не могу понять, как мне следует использовать asyncio, если вообще, для обработки этого. Я пытался предоставить asyncio.run в качестве обратного вызова для таймера ("timer": Timer(10.0, asyncio.run, args=[send_rem, [ctx.author, message]])), но, похоже, Python не позволяет мне указывать args для функции внутри функции. Как мне это обработать?

Ответ или решение

Для решения вашей проблемы с использованием корутин и таймеров в discord.py, необходимо понять, что основные компоненты библиотек asyncio и threading не всегда могут корректно взаимодействовать друг с другом. Ваша текущая реализация вызывает ошибку, потому что вы пытаетесь вызвать асинхронную функцию send_rem из потока, управляемого таймером, что приводит к несоответствию в вызовах.

Вы можете достичь необходимого результата, применяя подход с asyncio.create_task или loop.call_later, который позволяет избежать ошибок, связанных с ожиданием корутин. Ниже представлена исправленная версия вашего кода:

import discord
from discord.ext import commands
import asyncio

bot = commands.Bot(command_prefix='!')

user_events = {}

@bot.command()
async def reminder(ctx: commands.Context, message: str) -> None:
    print("Scheduling reminder")
    task = asyncio.create_task(send_rem(ctx.author, message))
    await asyncio.sleep(5)  # Ожидание 5 секунд
    await task  # Ожидание завершения задачи

async def send_rem(author, message):
    print("Sending reminder")
    DM_CHNL = await author.create_dm()
    await DM_CHNL.send(message)

bot.run('YOUR_TOKEN')

Объяснение кода

  1. Удаление таймера: Заменим таймер на await asyncio.sleep(5), который не будет блокировать основной поток, позволяя вашему коду оставаться асинхронным.

  2. Создание задачи: Используем asyncio.create_task() для запуска корутины send_rem в фоновом режиме. Это позволяет нам контролировать её выполнение и при этом не блокировать основной поток приложения.

  3. Асинхронный вызов: Вместо запуска функции в отдельном потоке и ожидания её завершения, мы отслеживаем ожидание с помощью стандартных средств asyncio. Таким образом, избегается проблема с ‘coroutine was never awaited’.

Преимущества данного подхода

  • Чистота кода: Код становится более чистым и легким для понимания, поскольку избегает прямого взаимодействия с потоками.
  • Производительность: Используя асинхронные вызовы, ваша программа может обрабатывать другие команды и задачи, пока ожидает выполнения корутин, что делает её более отзывчивой.
  • Устойчивость: Этот подход также более устойчив к ошибкам и исключениям, связанным с многопоточностью.

Заключение

Таким образом, для того чтобы асинхронная функция могла корректно работать с таймерами в контексте discord.py, всё, что вам нужно, – это использование await asyncio.sleep(). Это позволяет без дополнительных сложностей отслеживать выполнение и полностью использовать преимущества асинхронного программирования.

Если у вас есть дополнительные вопросы или нужно объяснить что-то ещё, не стесняйтесь спрашивать!

Оцените материал
Добавить комментарий

Капча загружается...