Вопрос или проблема
Я пытаюсь создать простой бот, который может отправлять эхо-сообщение, которое вы отправили в личных сообщениях, через определенное время. Вот команда и функция, которую она вызывает внутри таймера:
@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')
Объяснение кода
-
Удаление таймера: Заменим таймер на
await asyncio.sleep(5)
, который не будет блокировать основной поток, позволяя вашему коду оставаться асинхронным. -
Создание задачи: Используем
asyncio.create_task()
для запуска корутиныsend_rem
в фоновом режиме. Это позволяет нам контролировать её выполнение и при этом не блокировать основной поток приложения. -
Асинхронный вызов: Вместо запуска функции в отдельном потоке и ожидания её завершения, мы отслеживаем ожидание с помощью стандартных средств asyncio. Таким образом, избегается проблема с ‘coroutine was never awaited’.
Преимущества данного подхода
- Чистота кода: Код становится более чистым и легким для понимания, поскольку избегает прямого взаимодействия с потоками.
- Производительность: Используя асинхронные вызовы, ваша программа может обрабатывать другие команды и задачи, пока ожидает выполнения корутин, что делает её более отзывчивой.
- Устойчивость: Этот подход также более устойчив к ошибкам и исключениям, связанным с многопоточностью.
Заключение
Таким образом, для того чтобы асинхронная функция могла корректно работать с таймерами в контексте discord.py
, всё, что вам нужно, – это использование await asyncio.sleep()
. Это позволяет без дополнительных сложностей отслеживать выполнение и полностью использовать преимущества асинхронного программирования.
Если у вас есть дополнительные вопросы или нужно объяснить что-то ещё, не стесняйтесь спрашивать!