Discord.py 带线程,RuntimeError: Timeout context manager 应该在任务内部使用

Posted

技术标签:

【中文标题】Discord.py 带线程,RuntimeError: Timeout context manager 应该在任务内部使用【英文标题】:Discord.py with threads, RuntimeError: Timeout context manager should be used inside a task 【发布时间】:2021-08-31 20:13:25 【问题描述】:

所以我正在创建一个不和谐的机器人,它会不断地抓取网页,检查商品是否有货。当商品有货时,在聊天中发送消息。由于抓取是不断循环的,我想我会把它放在一个线程中,每个需要抓取的页面一个线程。问题是每个线程都产生相同的错误,

RuntimeError: Timeout context manager should be used inside a task

有谁知道为什么,或者对我正在尝试做的事情有更好的方法吗?

async def searchPage(input_url):

 currentUrl = input_url
 driver = webdriver.Chrome(options=options, executable_path=DRIVER_PATH)
 driver.get(currentUrl)

 while True:
     try:
         shipIt = driver.find_element_by_css_selector('[data-test="shippingBlock"]')
         await alertUsers(currentUrl)
         await asyncio.sleep(5)
     except NoSuchElementException:
         continue

 #driver.quit()

async def alertUsers(current_input):

 channel = client.get_channel(795513308869296181)
 await channel.send(f'Go buy this: current_input')

async def create_threads():

 thread1 = threading.Thread(target=asyncio.run, args=(searchPage("https://www.target.com/p/set-of-2-kid-century-modern-kids-chairs-b-spaces/-/A-81803789?preselect=80929605#lnk=sametab"),))
 thread1.start()
 thread2 = threading.Thread(target=asyncio.run, args=(searchPage("https://www.target.com/p/wrangler-men-s-big-tall-relaxed-fit-jeans-with-flex/-/A-79321830?preselect=52428349#lnk=sametab"),))
 thread2.start()

@client.event
async def on_ready():
 guild = discord.utils.get(client.guilds, name=GUILD)
 print(
     f'client.user is connected to the following guild:\n'
     f'guild.name(id: guild.id)'
 )
 await create_threads()


client.run(TOKEN)

【问题讨论】:

一些框架不喜欢在线程中运行 - 即所有 GUI 框架都必须在主线程中运行窗口和小部件。而且你可能有类似的问题 - 在线程内部你不能使用在主线程中创建的async。您应该在线程中作为正常功能运行searchPage,并将结果发送到主线程,主线程应该向不和谐发送一些消息。您可以使用全局变量或更好的queue 来发送结果。并且主线程应该运行一些定期检查队列并将消息发送到不和谐的功能。据我所知discord@task.loop(second=...) 可以定期运行。 兄弟不要使用线程...只需将所有网页拉取为异步请求即可。您没有足够的线程来处理所有这些项目,使用线程会很快变得混乱。 【参考方案1】:

有些框架不喜欢在threads 中运行 - 即所有 GUI 框架都必须在主线程中运行窗口和小部件。他们被称为not thread-safe

您可能也有类似的问题 - 在 thread 内部,您不能使用在主线程中创建的 async

你应该在新线程中运行searchPage 作为正常功能,并将结果发送到主线程,主线程应该发送一些消息给不和谐。您可以使用全局变量或更好的queue 来发送结果。并且主线程应该运行一些定期检查queue并向discord发送消息的函数。

Discord 有 @tasks.loop(second=...) 可以定期运行的功能。


最少的工作代码

import discord
from discord.ext import tasks
import os
import time
import queue
import threading
from selenium import webdriver

MY_CHANNEL = 795513308869296181    
     
# queue to communicate with threads
queue = queue.Queue()

client = discord.Client()

# --- normal functions ---

def searchPage(url, queue):
     driver = webdriver.Chrome()
     #driver = webdriver.Chrome(options=options, executable_path=DRIVER_PATH)
    
     while True:
         try:
             driver.get(url)
             ship_it = driver.find_element_by_css_selector('[data-test="shippingBlock"]')
             #print('[DEBUG] queue put:', url)
             queue.put(url)
             time.sleep(5)
         except NoSuchElementException as ex:
             #print('[DEBUG] queue put: not found')
             #queue.put('not found')
             #print('Exception:', ex)
             pass
    
     #driver.quit()

def create_threads():
     urls = [
         "https://www.target.com/p/set-of-2-kid-century-modern-kids-chairs-b-spaces/-/A-81803789?preselect=80929605#lnk=sametab",
         "https://www.target.com/p/wrangler-men-s-big-tall-relaxed-fit-jeans-with-flex/-/A-79321830?preselect=52428349#lnk=sametab",
     ]

     for url in urls:
        t = threading.Thread(target=searchPage, args=(url, queue))
        t.start()     

# --- async functions ---

@tasks.loop(seconds=5)    
async def alert_users():
     #print('[DEBUG] alert_users')   
     if not queue.empty():
         current_input = queue.get()
         channel = client.get_channel(MY_CHANNEL)
         await channel.send(f'Go buy this: current_input')
        
@client.event
async def on_ready():     
     create_threads()  
     alert_users.start()  # start `task.loop`

TOKEN = os.getenv('DISCORD_TOKEN')
client.run(TOKEN)

【讨论】:

效果很好!感谢您的回答

以上是关于Discord.py 带线程,RuntimeError: Timeout context manager 应该在任务内部使用的主要内容,如果未能解决你的问题,请参考以下文章

C-Python asyncio:在线程中运行 discord.py

Discord.py 从后台线程关闭 Bot

discord.py,同时使用斜杠命令和前缀

Discord.py 等级卡添加文字(枕头)

Discord.py:在检查中使用特殊响应导致不同的输出

discord.py 你能从 discord 标签中获取用户对象吗?