adapap
adapap

Reputation: 665

Discord.py background task breaks but task is still pending (using websocket)

I'm very inexperienced with asyncio and asynchronous programming in general, so i've been having a hard time trying to use a synchronous websocket module with the async discord.py module. I am trying to setup a bot which constantly listens for websocket messages, and if it receives data, it performs some calculations and sends a message in discord. This should run indefinitely, and the websocket changes its origin routinely. Here is some commented code of what i'm trying to accomplish:

import requests
import websocket
import discord
import asyncio
from time import sleep

client = discord.Client() # Initialize the discord client

class Wrapper: # This just holds some variables so that I can use them without worrying about scope
  data = None
  first_time = True
  is_running = False
  socket_url = None

async def init_bot(): # The problem begins here
  def on_message(ws, message):
    if is_valid(message['data']): # is_valid is just a name for a special comparison I do with the message
      Wrapper.data = message['data']
      print('Received valid data')

  def on_error(ws, error):
    print('There was an error connecting to the websocket.')

  ws = websocket.WebSocketApp(Wrapper.socket_url,
    on_message=on_message,
    on_error=on_error)

  while True:
    ws.run_forever() # I believe this is connected to the discord event loop?
    # Using ws.close() here breaks the program when it receives data for the second time

async def start():
  await client.wait_until_ready()
  def get_response(hq_id, headers):
    response = requests.get('my_data_source')
    try:
      return json.loads(response.text)
    except:
      return None

  print('Starting websocket')
  await init_bot()
  while True: # This should run forever, but the outer coroutine gets task pending errors
    print('Running main loop')
    if Wrapper.first_time:
      Wrapper.first_time = False
      on_login = await client.send_message(Config.public_channel, embed=Config.waiting_embed()) # Sends an embed to a channel
    while Wrapper.socket_url is None:
      response = get_response(ID, HEADERS)
      try:
        Wrapper.socket_url = response['socket_url'] # Assume this sets the socket url to a websocket in the form ws://anyhost.com
        await client.edit_message(on_login, embed=Config.connect_embed())
        Wrapper.is_running = True
        await asyncio.sleep(3)
      except:
        await asyncio.sleep(60) # The response will only sometimes include a proper socket_url, so we wait for one
    if Wrapper.is_running:
      while Wrapper.data is None: # Is this blocking? I essentially want this while loop to end when we have data from line 18
        await asyncio.sleep(1)
      if Wrapper.data is not None:
        data_message = await client.send_message(Config.public_channel, embed=Wrapper.data[0])
        await client.add_reaction(ans_message, '👍')
        await client.add_reaction(ans_message, '👎')
        if Wrapper.data[1] == True: # Marks the last message, so we want to reset the bot to its initial state
          Wrapper.is_running = False
          Wrapper.first_time = True
          Wrapper.socket_url = None
          await asyncio.sleep(100) # Sleep for a period of time in order to make sure the socket url is closed
        Wrapper.data = None
        await asyncio.sleep(3)
  print('While loop ended?')

@client.event
async def on_ready():
  print(f'My Bot\n')

client.loop.create_task(start())
client.run('<TOKEN>')

I've tried several variations of the above, but the error I typically get is something along these lines:

File "mybot.py", line 247, in <module>
    client.loop.create_task(start())
task: <Task pending coro=<start() running at mybot.py> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_connect_done(1016)(), <TaskWakeupMethWrapper object at 0x000002545FFFC8B8>()]

Upvotes: 0

Views: 2280

Answers (1)

user4815162342
user4815162342

Reputation: 154856

You cannot just mix asyncio-aware code, such as discord, with synchronous websockets code. Since nothing is awaited in init_bot, calling await init_bot() completely stops the event loop.

Instead, you need to run the websocket code (init_bot function in your case) in a separate thread and await an appropriate event. For example:

def init_bot(loop, w):
    def on_message(ws, message):
        w.data = message['data']
        loop.call_soon_threadsafe(w.event.set)
    # ...

async def start():
    # ...
    loop = asyncio.get_event_loop()
    w = Wrapper()
    w.event = asyncio.Event()
    threading.Thread(target=lambda: init_bot(loop, w)).start()
    # ...
    # instead of while Wrapper.data is None ...
    await w.event.wait()
    # ... process data
    w.event.clear()

Upvotes: 2

Related Questions