tl; dr Как мой бот может асинхронно ожидать реакции на множественные сообщения?
Я добавляю команду "rock-paper-scissors (rps)" к своемуРаздор бот.Пользователи могут вызвать команду, которую можно вызвать, введя .rps
вместе с необязательным параметром, указав пользователя для игры.
.rps @TrebledJ
При вызове бот отправит прямое сообщение (DM) пользователю, которыйВызвал его и целевой пользователь (из параметра).Затем эти два пользователя реагируют на свою DM либо ✊, ?, либо ✌️.
Теперь я пытаюсь заставить это работать асинхронно.В частности, бот будет отправлять DM обоим пользователям (асинхронно) и ждать их реакции (асинхронно).Пошаговый сценарий:
Scenario (Asynchronous):
1. User A sends ".rps @User_B"
2. Bot DMs User A and B.
3. User A and B react to their DMs.
4. Bot processes reactions and outputs winner.
(См. Также: примечание 1)
Поскольку цель состоит в том, чтобы прослушивать ожидание реакции от нескольких сообщений,Я попытался создать две отдельные темы / пулы.Вот три попытки:
multiprocessing.pool.ThreadPool
multiprocessing.Pool
concurrent.futures.ProcessPoolExecutor
К сожалению, все три не сделалит работать(Может быть, я что-то неправильно реализовал?)
Следующий код показывает командную функцию (rps
), вспомогательную функцию (rps_dm_helper
) и три (неудачные) попытки.Все попытки используют разные вспомогательные функции, но основная логика одинакова.Первая попытка была раскомментирована для удобства.
import asyncio
import discord
from discord.ext import commands
import random
import os
from multiprocessing.pool import ThreadPool # Attempt 1
# from multiprocessing import Pool # Attempt 2
# from concurrent.futures import ProcessPoolExecutor # Attempt 3
bot = commands.Bot(command_prefix='.')
emojis = ['✊', '?', '✌']
# Attempt 1 & 2
async def rps_dm_helper(player: discord.User, opponent: discord.User):
if player.bot:
return random.choice(emojis)
message = await player.send(f"Playing Rock-Paper-Scissors with {opponent}. React with your choice.")
for e in emojis:
await message.add_reaction(e)
try:
reaction, _ = await bot.wait_for('reaction_add',
check=lambda r, u: r.emoji in emojis and r.message.id == message.id and u == player,
timeout=60)
except asyncio.TimeoutError:
return None
return reaction.emoji
# # Attempt 3
# def rps_dm_helper(tpl: (discord.User, discord.User)):
# player, opponent = tpl
#
# if player.bot:
# return random.choice(emojis)
#
# async def rps_dm_helper_impl():
# message = await player.send(f"Playing Rock-Paper-Scissors with {opponent}. React with your choice.")
#
# for e in emojis:
# await message.add_reaction(e)
#
# try:
# reaction, _ = await bot.wait_for('reaction_add',
# check=lambda r, u: r.emoji in emojis and r.message.id == message.id and u == player,
# timeout=60)
# except asyncio.TimeoutError:
# return None
#
# return reaction.emoji
#
# return asyncio.run(rps_dm_helper_impl())
@bot.command()
async def rps(ctx, opponent: discord.User = None):
"""
Play rock-paper-scissors!
"""
if opponent is None:
opponent = bot.user
# Attempt 1: multiprocessing.pool.ThreadPool
pool = ThreadPool(processes=2)
author_result = pool.apply_async(asyncio.run, args=(rps_dm_helper(ctx.author, opponent),))
opponent_result = pool.apply_async(asyncio.run, args=(rps_dm_helper(opponent, ctx.author),))
author_emoji = author_result.get()
opponent_emoji = opponent_result.get()
# # Attempt 2: multiprocessing.Pool
# pool = Pool(processes=2)
# author_result = pool.apply_async(rps_dm_helper, args=(ctx.author, opponent))
# opponent_result = pool.apply_async(rps_dm_helper, args=(opponent, ctx.author))
# author_emoji = author_result.get()
# opponent_emoji = opponent_result.get()
# # Attempt 3: concurrent.futures.ProcessPoolExecutor
# with ProcessPoolExecutor() as exc:
# author_emoji, opponent_emoji = list(exc.map(rps_dm_helper, [(ctx.author, opponent), (opponent, ctx.author)]))
### -- END ATTEMPTS
if author_emoji is None:
await ctx.send(f"```diff\n- RPS: {ctx.author} timed out\n```")
return
if opponent_emoji is None:
await ctx.send(f"```diff\n- RPS: {opponent} timed out\n```")
return
author_idx = emojis.index(author_emoji)
opponent_idx = emojis.index(opponent_emoji)
if author_idx == opponent_idx:
winner = None
elif author_idx == (opponent_idx + 1) % 3:
winner = ctx.author
else:
winner = opponent
# send to main channel
await ctx.send([f'{winner} won!', 'Tie'][winner is None])
bot.run(os.environ.get("BOT_TOKEN"))
Примечание
1 Сравните асинхронный сценарий с несинхроннымone:
Scenario (Non-Asynchronous):
1. User A sends ".rps @User_B"
2. Bot DMs User A.
3. User A reacts to his/her DM.
4. Bot DMs User B.
5. User B reacts to his/her DM.
6. Bot processes reactions and outputs winner.
Это было не так уж сложно реализовать:
...
@bot.command()
async def rps(ctx, opponent: discord.User = None):
"""
Play rock-paper-scissors!
"""
...
author_emoji = await rps_dm_helper(ctx.author, opponent)
if author_emoji is None:
await ctx.send(f"```diff\n- RPS: {ctx.author} timed out\n```")
return
opponent_emoji = await rps_dm_helper(opponent, ctx.author)
if opponent_emoji is None:
await ctx.send(f"```diff\n- RPS: {opponent} timed out\n```")
return
...
Но ИМХО, не асинхронность делает плохой UX.: -)