3

Making a Discord bot using discord.py, this is the first time I work with asyncio, and probably the first time I encountered something this frustrating in Python.

The point of this question isn't to teach me how to use asyncio, but instead to teach me how to avoid using it, even if it's not the right way to do things.

So I needed to run the discord client coroutines from regular def functions. After hours of searching I found this: asyncio.get_event_loop().run_until_complete(...). I set up a small script to test it out:

import asyncio

async def test():
    print('Success')

asyncio.get_event_loop().run_until_complete(test())

And it worked perfectly. So I went ahead and tried to use it in a discord bot:

import discord
import asyncio

client = discord.Client()

@client.event
async def on_ready():
    test()

def test():
    asyncio.get_event_loop().run_until_complete(run())

async def run():
    print('Success')

client.run('TOKEN_HERE')

And I got an error... Stacktrace/Output:

Success
Ignoring exception in on_ready
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\site-packages\discord\client.py", line 307, in _run_event
    yield from getattr(self, event)(*args, **kwargs)
  File "C:/Users/OverclockedSanic/PyCharm Projects/asyncio test/test.py", line 8, in on_ready
    test()
  File "C:/Users/OverclockedSanic/PyCharm Projects/asyncio test/test.py", line 11, in test
    asyncio.get_event_loop().run_until_complete(run())
  File "C:\Program Files\Python36\lib\asyncio\base_events.py", line 454, in run_until_complete
    self.run_forever()
  File "C:\Program Files\Python36\lib\asyncio\base_events.py", line 408, in run_forever
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running

What's weird is that "Success" part at the end... I tried some other tests to see if I could return data from the coroutine or execute more stuff, but it couldn't.

I even tried replacing asyncio.get_event_loop() with client.loop, which didn't work either.

I looked for like 2 days, still no solution. Any ideas?

EDIT: Replacing get_event_loop() with new_event_loop() as suggested in the comments raised this:

Ignoring exception in on_ready
Traceback (most recent call last):
  File "C:\Program Files\Python36\lib\site-packages\discord\client.py", line 307, in _run_event
    yield from getattr(self, event)(*args, **kwargs)
  File "C:/Users/USER/PyCharm Projects/asyncio test/test.py", line 8, in on_ready
    test()
  File "C:/Users/USER/PyCharm Projects/asyncio test/test.py", line 11, in test
    asyncio.new_event_loop().run_until_complete(run())
  File "C:\Program Files\Python36\lib\asyncio\base_events.py", line 454, in run_until_complete
    self.run_forever()
  File "C:\Program Files\Python36\lib\asyncio\base_events.py", line 411, in run_forever
    'Cannot run the event loop while another loop is running')
RuntimeError: Cannot run the event loop while another loop is running
4
  • Look through the examples on the github page. The important thing to note is that you don't have to handle the event loop directly, the discord.py module will take care of that for you Commented Oct 21, 2017 at 16:34
  • The only interesting thing I found there is background_task.py, and even that didn't help much... Commented Oct 21, 2017 at 17:37
  • Possible duplicate of Runtime error: Event loop is running Commented Oct 21, 2017 at 19:58
  • If you don’t want to make test a coroutine, you’ll need to run your code in a separate loop. asyncio.new_event_loop Commented Oct 22, 2017 at 0:14

1 Answer 1

3

Your problem seems to essentially be about mixing synchronous and asynchronous code. There are two possibilities:

1) If your non-async routines don't need to block, just to schedule some async task (e.g. send_message) to be run later, then they can simply call get_event_loop().create_task(). You can even use add_done_callback on the returned task if you want some other (non-async) routine to be called when the asynchronous operation is complete. (If the routine to be run is also non-async, then use get_event_loop().call_soon().)

2) If your non-async routines absolutely must block (which includes possibly awaiting an asynchronous routine), and cannot schedule the blocking operation for later, then you should not run them from the same thread as the main event loop. You can create a thread pool with concurrent.futures.ThreadPoolExecutor, and use asyncio.run_in_executor() to schedule your non-async routines, then await the result. And if they in turn need to call async routines, then run_until_complete() should work because now you're not running in a thread that already has an event loop. (But beware of threadsafety issues. You may need something like run_coroutine_threadsafe if you need to wait for something to run in the main event loop.)

If it helps, the asgiref package contains routines that can simplify this for you. They're designed for a slightly different purpose (web servers), but may also work for you. You can use await asgiref.sync.sync_to_async(func)(args) when you want to call a non-async routine from an async routine, which will run the routine in a thread pool, then use asgiref.sync.async_to_sync(func)(args) when you want to call an async routine from a non-async routine that's running inside that thread pool.

Sign up to request clarification or add additional context in comments.

2 Comments

I've since solved the issue on my own, and forgot about this question, but this seems like a useful solution nonetheless.
@OverclockedSanic, Can you share your solution?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.