использовать подпроцесс, чтобы избежать отсоединения бота discord.py в длительной задаче? - PullRequest
0 голосов
/ 03 декабря 2018

Я создал бота для своего сервера Discord, который переходит к API Reddit для данного поддредита и публикует 10 лучших результатов за день в чате Discord на основе введенных вами подредитов.Он не учитывает собственные сообщения и действительно только публикует картинки и GIF-изображения.Команда Discord message будет выглядеть примерно так: =get funny awww news programming, публикуя результаты для каждого subreddit по мере их получения из Reddit API (PRAW).Это работает без проблем.Я знаю, что способность бота поразить API и публиковать в разлад работает.

Я добавил еще одну команду =getshuffled, которая помещает все результаты из подредактов в большой список, а затем перетасовывает их перед публикацией.Это работает очень хорошо с запросом до ~ 50 subreddits.

Вот что мне нужно, чтобы помочь:

Поскольку это может быть такой большой список результатов, более 1000 результатов из 100+ подредакторов, бот падает надействительно большие запросы.Исходя из того, какую помощь я получил от моего вопроса вчера , я понимаю, что происходит не так.Бот запускается, он общается с моим сервером Discord, и когда я передаю ему длинный запрос, он перестает слишком долго разговаривать с сервером, пока выполняется вызов API Reddit, и в случае сбоя соединения Discord.

Итак, что я думаю , что мне нужно сделать, - это создать подпроцесс для кода, который идет в Reddit API и извлекает результаты (что, я думаю, позволит диссонансному соединению работать), изатем передайте эти результаты НАЗАД боту, когда он закончит ....

Или ... это то, что Asyncio может обрабатывать самостоятельно ...

У меня естьтрудные времена с вызовом подпроцесса, как я и знал.

По сути, мне либо нужна помощь с этим обманом подпроцесса, либо мне нужно знать, являюсь ли я идиотом, и Asyncio справится со всем этим для меня,Я думаю, что это только один из тех примеров «я не знаю, чего я не знаю».

Итак, резюмируем: бот работал нормально с перемешанным меньшим количеством субреддитов.Он проходит через отправленные аргументы (которые являются subreddits), захватывает информацию для каждого поста, а затем перетасовывается перед публикацией ссылок на раздор.Проблема в том, что это большой набор подредакторов ~ 50+.Чтобы заставить его работать с большей суммой, мне нужно, чтобы вызов Reddit НЕ блокировал соединение с основным диссонансом, и именно поэтому я пытаюсь создать подпроцесс.

Версия Python 3.6 и Discord.py версия 0.16.12 Этот бот размещен и работает на PythonAnywhere

Код:

from redditBot_auth import reddit

import discord
import asyncio
from discord.ext.commands import Bot
#from discord.ext import commands
import platform
import subprocess
import ast

client = Bot(description="Pulls posts from Reddit", command_prefix="=", pm_help = False)

@client.event
async def on_ready():
    return await client.change_presence(game=discord.Game(name='Getting The Dank Memes')) 

def is_number(s):
    try:
        int(s)
        return True
    except:
        pass

def show_title(s):
    try:
        if s == 'TITLES':
            return True
    except:
        pass

async def main_loop(*args, shuffled=False):
    print(type(args))

    q=10

    #This takes a integer value argument from the input string.
    #It sets the number variable,
    #Then deletes the number from the arguments list.
    title = False
    for item in args:
        if is_number(item):
            q = item
            q = int(q)
            if q > 15:
                q=15
            args = [x for x in args if not is_number(x)]

        if show_title(item):
            title = True
            args = [x for x in args if not show_title(x)]

    number_of_posts = q * len(args)
    results=[]

    TESTING = False #If this is turned to True, the subreddit of each post will be posted. Will use defined list of results


    if shuffled == False: #If they don't want it shuffled

        for item in args:
            #get subreddit results
            #post links into Discord as it gets them
            #The code for this works

    else: #if they do want it shuffled
        output = subprocess.run(["python3.6", "get_reddit.py", "*args"])
        results = ast.literal_eval(output.decode("ascii"))
        # ^^ this is me trying to get the results back from the other process.

.Это мой файл get_reddit.py:

#THIS CODE WORKS, JUST NEED TO CALL THE FUNCTION AND RETURN RESULTS
#TO THE MAIN_LOOP FUNCTION

from redditBot_auth import reddit
import random

def is_number(s):
    try:
        int(s)
        return True
    except:
        pass

def show_title(s):
    try:
        if s == 'TITLES':
            return True
    except:
        pass

async def get_results(*args, shuffled=False):

    q=10

    #This takes a integer value argument from the input string.
    #It sets the number variable,
    #Then deletes the number from the arguments list.
    title = False
    for item in args:
        if is_number(item):
            q = item
            q = int(q)
            if q > 15:
                q=15
            args = [x for x in args if not is_number(x)]

        if show_title(item):
            title = True
            args = [x for x in args if not show_title(x)]

    results=[]

    TESTING = False #If this is turned to True, the subreddit of each post will be posted. Will use defined list of results.
    NoGrabResults = False

    #This pulls the data and creates a list of links for the bot to post

    if NoGrabResults == False:
        for item in args:
            try:
                #get the posts
                #put them in results list    

            except Exception as e:
                #handle error
                pass

        try:
            #print('____SHUFFLED___')
            random.shuffle(results)
            random.shuffle(results)
            random.shuffle(results)

        except:
            #error stuff

        print(results)
#I should be able to read that print statement for the results, 
#and then use that in the main bot function to post the results.

.

@client.command()
async def get(*args, brief="say '=get' followed by a list of subreddits", description="To get the 10 Top posts from a subreddit, say '=get' followed by a list of subreddits:\n'=get funny news pubg'\n would get the top 10 posts for today for each subreddit and post to the chat."):
    #sr = '+'.join(args)
    await main_loop(*args)

#THIS POSTS THE POSTS RANDOMLY   
@client.command()
async def getshuffled(*args, brief="say '=getshuffled' followed by a list of subreddits", description="Does the same thing as =get, but grabs ALL of the posts and shuffles them, before posting."):

    await main_loop(*args, shuffled=True)


client.run('my ID')

ОБНОВЛЕНИЕ: Следуя совету, команда прошла через ThreadPoolExecutor, как показано:

async def main(*args, shuffled):

    if shuffled==True:

        with concurrent.futures.ThreadPoolExecutor() as pool:
            results = await asyncio.AbstractEventLoop().run_in_executor(
                executor=pool, func=await main_loop(*args, shuffled=True))
            print('custom thread pool', results)

, но это все равно приводит к ошибкам при попытке сценарияпоговорить с Discord:

ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<Client._run_event() running at /home/GageBrk/.local/lib/python3.6/site-packages/discord/client.py:307> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f28acd8db28>()]>>
Event loop is closed
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
...

Он отправляет результаты правильно, но Discord все еще теряет связь.

1 Ответ

0 голосов
/ 03 декабря 2018

praw опирается на библиотеку requests, которая является синхронной, что означает, что код блокируется.Это может привести к зависанию вашего бота, если выполнение кода блокировки занимает слишком много времени.

Чтобы обойти это, можно создать отдельный поток, который обрабатывает код блокировки.Ниже приведен пример этого.Обратите внимание, как blocking_function будет использовать time.sleep для блокировки в течение 10 минут (600 секунд).Этого должно быть более чем достаточно, чтобы заморозить и в конечном итоге вывести из строя бота.Однако, поскольку функция находится в своем собственном потоке с использованием run_in_executor, бот продолжает работать в обычном режиме.

import time
import asyncio
from discord.ext import commands
from concurrent.futures import ThreadPoolExecutor

def blocking_function():
    print('entering blocking function')
    time.sleep(600)
    print('sleep has been completed')
    return 'Pong'

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

@client.event
async def on_ready():
    print('client ready')

@client.command()
async def ping():
    loop = asyncio.get_event_loop()
    block_return = await loop.run_in_executor(ThreadPoolExecutor(), blocking_function)
    await client.say(block_return)

client.run('token')
...