Skip to content
Advertisement

Discord.py with threads, RuntimeError: Timeout context manager should be used inside a task

So I’m creating a discord bot that is constantly scraping a webpage, checking if the item is in stock. When the item becomes in stock, send a message in the chat. Since the scraping is in a constant loop, I figured I would put that in a thread, one for each page that needs to be scraped. The problem is that each thread is producing the same error,

RuntimeError: Timeout context manager should be used inside a task

Does anyone know why, or a better approach to what I am trying to do?

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)

Advertisement

Answer

Some frameworks don’t like to run in threads – i.e. all GUI frameworks has to run windows and widgets in main thread. They are called not thread-safe.

And you may have similar problem – inside thread you can’t use async created in main thread.

You should run searchPage in new thread as normal function and send results to main thread which should send some message to discord. You can use global variable or better queue to send results. And main thread should run some function which periodically check queue and send messages to discord.

Discord has @tasks.loop(second=...) which can run periodically function.


Minimal working code

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)
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement