I am currently working on a project where I am looking for information updates and then posting status messages to a slack channel. This is my first python project and I am a little out of the loop with what is going wrong. However, it appears that the RuntimeError: There is no current event loop in thread 'MainThread' error that I am getting is caused by having two async requests in my main function.
I was wondering if anyone would be able to tell me what best practice would be and how i could avoid any more issues?
def main():
configure()
print("the project has started")
asyncio.run(post_message("the project has started"))
event_filter = [my api call to another service goes here]
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(
asyncio.gather(
log_loop(event_filter, 2)))
finally:
# close loop to free up system resources
loop.close()
async def post_message(message):
try:
client = AsyncWebClient(token=os.getenv('SLACK_BOT_TOKEN'))
response = await client.chat_postMessage(channel='#notifications', text=message)
assert response["message"]["text"] == message
except SlackApiError as e:
assert e.response["ok"] is False
assert e.response["error"] # str like 'invalid_auth', 'channel_not_found'
print(f"Got an error: {e.response['error']}")
It seems to me that the call asyncio.run(post_message("the project has started")) is not playing well with my loop = asyncio.get_event_loop() but again I am unsure why.
Any help would be much appreciated!
EDIT
Here is the full traceback as requested:
Traceback (most recent call last):
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/runpy.py", line 194, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/runpy.py", line 87, in _run_code
exec(code, run_globals)
File "myprojectpath/__main__.py", line 4, in <module>
app.main()
File "myprojectpath/app.py", line 54, in main
loop = asyncio.get_event_loop()
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/asyncio/events.py", line 639, in get_event_loop
raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'MainThread'.
This recreates your error:
import asyncio
async def test():
pass
def main():
asyncio.run(test())
loop = asyncio.get_event_loop()
loop.run_until_complete(test())
The issue is that asyncio.run creates an event loop, runs your coroutine, and then closes the event loop. Therefore you can't use that loop later on. Instead, change main into an async function and just use await OR use asyncio.run for your gather call. Try avoiding using the loop api unless you have to.
Related
I am using asyncio and aiohttp to make concurrent requests. I've recently upgraded Python to version 3.8.0 and I'm getting a RuntimeError: Event loop is closed after the program has run.
import asyncio
import aiohttp
async def do_call(name, session):
async with session.get('https://www.google.be') as response:
await response.text()
return 'ok - {}'.format(name)
async def main():
async with aiohttp.ClientSession() as session:
tasks = [do_call(str(i), session) for i in range(0,4)]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
I do get a valid result from asyncio.gather(), but when exiting the exception is raised.
I'd like to change the code so it doesn't run into exceptions.
The traceback is as follows:
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001E9A92079D0>
Traceback (most recent call last):
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\proactor_events.py", line 116, in __del__
self.close()
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\proactor_events.py", line 108, in close
self._loop.call_soon(self._call_connection_lost, None)
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 711, in call_soon
self._check_closed()
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 504, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed`
I think this is most likely an aiohttp bug. Specifically, I found this issue on their github: https://github.com/aio-libs/aiohttp/issues/4324
I realise this doesn't necessarily help you, but maybe you can switch back and stop banging your head against a wall. Your code is fine!
I solved this by not calling my_loop.close() after I was done using it. Closing the event loop this way caused the error to be thrown even after I had gotten all the responses I was expecting.
I have this code which, in theory, will check for how many messages have been sent in a channel, and will do this at a certain time each day. Here is the code:
def checkTime():
# This function runs periodically every 1 second
threading.Timer(1, checkTime).start()
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
print("Current Time =", current_time)
if(current_time == '21:35:20'): # check if matches with the desired time
print("starting")
channel = bot.get_channel(this is where I put the channel ID, just hiding it in here)
counter = 0
for message in channel.history():
counter += 1
print(counter)
checkTime()
However, once the time gets to 21:35:20, this error message appears:
starting
Exception in thread Thread-9:
Traceback (most recent call last):
File "C:\Users\jackt\AppData\Local\Programs\Python\Python39\lib\threading.py", line 954, in _bootstrap_inner
self.run()
File "C:\Users\jackt\AppData\Local\Programs\Python\Python39\lib\threading.py", line 1266, in run
self.function(*self.args, **self.kwargs)
File "C:\Users\jackt\Desktop\bot.py", line 51, in checkTime
for message in channel.history():
File "C:\Users\jackt\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\abc.py", line 1087, in history
return HistoryIterator(self, limit=limit, before=before, after=after, around=around, oldest_first=oldest_first)
File "C:\Users\jackt\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\iterators.py", line 256, in __init__
self.messages = asyncio.Queue()
File "C:\Users\jackt\AppData\Local\Programs\Python\Python39\lib\asyncio\queues.py", line 35, in __init__
self._loop = events.get_event_loop()
File "C:\Users\jackt\AppData\Local\Programs\Python\Python39\lib\asyncio\events.py", line 642, in get_event_loop
raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Thread-9'.
I'm using "from datetime import datetime" and "import threading"
Please can you let me know what my code should look like to prevent this error. Thank you!
In General, with discord.py you should refrain from using threads at all possible as there is almost always an asyncio friendly way to accomplish the task.
As far as the reason for your problem is because asyncio is single-threaded the event loop that discord.py runs on is only on 1 thread, so when you try to access the event loop from another thread it will error because it assumes the event loop is on the same thread as you are calling from.
Now given this, asyncio does have threadsafe methods such as call_soon_threadsafe
But for this question specifically, discord.py has an extension dedicated to doing periodic tasks in the background: called the tasks extension: https://discordpy.readthedocs.io/en/latest/ext/tasks/index.html
I am using asyncio and aiohttp to make concurrent requests. I've recently upgraded Python to version 3.8.0 and I'm getting a RuntimeError: Event loop is closed after the program has run.
import asyncio
import aiohttp
async def do_call(name, session):
async with session.get('https://www.google.be') as response:
await response.text()
return 'ok - {}'.format(name)
async def main():
async with aiohttp.ClientSession() as session:
tasks = [do_call(str(i), session) for i in range(0,4)]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
I do get a valid result from asyncio.gather(), but when exiting the exception is raised.
I'd like to change the code so it doesn't run into exceptions.
The traceback is as follows:
Exception ignored in: <function _ProactorBasePipeTransport.__del__ at 0x000001E9A92079D0>
Traceback (most recent call last):
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\proactor_events.py", line 116, in __del__
self.close()
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\proactor_events.py", line 108, in close
self._loop.call_soon(self._call_connection_lost, None)
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 711, in call_soon
self._check_closed()
File "C:\Users\Jonas\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py", line 504, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed`
I think this is most likely an aiohttp bug. Specifically, I found this issue on their github: https://github.com/aio-libs/aiohttp/issues/4324
I realise this doesn't necessarily help you, but maybe you can switch back and stop banging your head against a wall. Your code is fine!
I solved this by not calling my_loop.close() after I was done using it. Closing the event loop this way caused the error to be thrown even after I had gotten all the responses I was expecting.
I wrote a telegram bot in python using the telepot library. The bot itself is working. Since I also have some other stuff I need to do periodically while running the bot (namely writing stuff in logs), I want the bot to run in its own thread.
The telegram bot's code is a file called telebot.py while the threading code is in one called plantbot.py
I first tried simply creating a new thread that calls my run_bot() function, which didn't work and gave me the following RuntimeError:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "plantbot.py", line 12, in start_bot
telebot.run_bot()
File "/srv/plantbot/telebot.py", line 108, in run_bot
loop = asyncio.get_event_loop()
File "/usr/lib/python3.5/asyncio/events.py", line 671, in get_event_loop
return get_event_loop_policy().get_event_loop()
File "/usr/lib/python3.5/asyncio/events.py", line 583, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-1'.
Here is the code I used for that:
plantbot.py
def start_bot():
telebot.run_bot()
bot = threading.Thread(target=start_bot, daemon=True)
bot.start()
print("Bot started...")
bot.join()
telebot.py
[lots of unrelated code]
bot = telepot.aio.Bot(TOKEN)
def run_bot():
loop = asyncio.get_event_loop()
loop.create_task(MessageLoop(bot, {'chat': on_chat_message, 'callback_query': on_callback_query}).run_forever())
print('Listening ...')
loop.run_forever()
I then tried around a little with the event loops until I ended up creating the event loop in my main file (plantbot.py) which looked like this:
plantbot.py
def start_bot(loop):
telebot.run_bot(loop)
loop = asyncio.new_event_loop()
bot = threading.Thread(target=start_bot, args=(loop,), daemon=True)
bot.start()
print("Bot started...")
bot.join()
telebot.py
[...]
bot = telepot.aio.Bot(TOKEN)
def run_bot(loop):
loop.create_task(MessageLoop(bot, {'chat': on_chat_message, 'callback_query': on_callback_query}).run_forever())
print('Listening ...')
loop.run_forever()
I also tried another variation:
plantbot.py
def start_bot(loop):
asyncio.set_event_loop(loop)
loop.create_task(telebot.task.run_forever())
loop.run_forever()
loop = asyncio.new_event_loop()
bot = threading.Thread(target=start_bot, args=(loop,), daemon=True)
bot.start()
print("Bot started...")
bot.join()
telebot.py
[...]
bot = telepot.aio.Bot(TOKEN)
task = MessageLoop(bot, {'chat': on_chat_message, 'callback_query': on_callback_query})
Unfortunately, none of these worked.
My question is thus, how do I properly run an event loop in its own designated thread? Specifically: How do I run a telegram bot in its own thread?
I did end up finding a solution, though it's not a satisfying one:
I am now running the bot in the main thread and put everything else into other threads. It does work that way around.
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
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.