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.
Related
The main idea is to make a discord integration to the existing telegram bot written with python-telegram-bot library. So here I was thinking about running multiple coroutines with asyncio.gather and asyncio.run (I'm using python 3.10).
Right now I'm not doing anything with my existing code. Instead to avoid any possible blocking code in my main app for now I decided to write this simple program which creates a bot and just push a message to logs if message is received:
import logging as log
import asyncio
import os
import discord
from discord.ext import commands
log.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=log.DEBUG)
client = commands.Bot(command_prefix="/")
#client.event
async def on_ready():
log.info('Connected to Discord')
#client.event
async def on_message(message):
log.info("Got message")
async def start_discord():
await client.start(os.environ.get('DISCORD_TOKEN'))
async def main():
await asyncio.gather(start_discord())
asyncio.run(main())
When I start it everything looks ok, but after a while I started to get exceptions that heartbeat is blocked and WebSocket connection timed out. In addition to that none of both events are triggered no matter how many messages are sent:
2022-07-04 17:50:52,378 - discord.gateway - WARNING - Shard ID None heartbeat blocked for more than 10 seconds.
Loop thread traceback (most recent call last):
File "/mnt/d/Documents/Projects/test.py", line 28, in <module>
asyncio.run(main())
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.10/asyncio/base_events.py", line 633, in run_until_complete
self.run_forever()
File "/usr/lib/python3.10/asyncio/base_events.py", line 600, in run_forever
self._run_once()
File "/usr/lib/python3.10/asyncio/base_events.py", line 1860, in _run_once
event_list = self._selector.select(timeout)
File "/usr/lib/python3.10/selectors.py", line 469, in select
fd_event_list = self._selector.poll(timeout, max_ev)
But if I replace asyncio.run(main) with asyncio.get_event_loop().run_until_complete(main()) everything starts to work correctly. I see that both events are triggered and message is received. But as I see get_event_loop is deprecated and it's suggested to use asyncio.run() from 3.10:
/mnt/d/Documents/Projects/test.py:29: DeprecationWarning: There is no current event loop
asyncio.get_event_loop().run_until_complete(main())
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.
So, I have coded a Discord Bot, but I want to run it in a thread, because I need pyramid for a webhook with GitHub. I will run it side by side, because the bot should write a message, when I push a update for my website hosted by GitHub Pages.
I got the pyramid server running but the bot wont start and when I try to run the bot normally, It runs quite nice.
The errors I got are:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
self.run()
File "/usr/lib/python3.8/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "/home/nikurasu/PycharmProjects/pyramidtest/test2.py", line 35, in discordthread
client = MyClient()
File "/home/nikurasu/PycharmProjects/pyramidtest/venv/lib/python3.8/site-packages/discord/client.py", line 206, in __init__
self.loop = asyncio.get_event_loop() if loop is None else loop
File "/usr/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 'Thread-1'.
I simplified the code to exclude other shitcode written by me, but even when I use that simple code, the bot do not want to run:
import threading
import discord
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def hello_word(request):
return Response('Hello World')
def hello_jonas(request):
return Response('<b>Hello, Jonas</b>')
def pyramidthread():
with Configurator() as config:
config.add_route('hello', '/')
config.add_view(hello_word, route_name='hello')
config.add_route('jonas', '/jonas')
config.add_view(hello_jonas, route_name='jonas')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()
def discordthread():
token = 'nothing'
class MyClient(discord.Client):
async def on_ready(self):
print('Eingeloggt als', self.user)
print("Izuna versucht zu starten")
client = MyClient()
client.run(token)
t1 = threading.Thread(target=discordthread)
t2 = threading.Thread(target=pyramidthread)
t1.start()
t2.start()
I hope someone here can help me^^
I'm trying to make a Discord Bot and it has to check an API every minute or so and then send a message to a channel.
But for some reason, the loop breaks and ends the task.
It's my first time using asyncio and discord.py
Traceback (most recent call last):
File "bot.py", line 207, in <module>
client.loop.run_forever()
File "/usr/local/lib/python3.6/asyncio/base_events.py", line 407, in run_forever
self._check_closed()
File "/usr/local/lib/python3.6/asyncio/base_events.py", line 358, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
Task was destroyed but it is pending!
task: <Task pending coro=<my_background_task() running at bot.py:193> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0xb52dbd10>()]>>
The source code for the script is this one:
bot.py
Just want to have my_background_task() to run forever, or at least restart if it breaks.
Make the bot run the async task
async def bg():
for i in range(3):
print("Background tast")
await asyncio.sleep(1)
#bot.event
async def on_ready():
print("Ready!")
bot.loop.create_task(bg())
I want to run a function in background. so I use Threading in my code.
but return error ValueError: signal only works in main thread and don't know about two things:
what is the main thread
how to solve this problem :)
views.py
def callback(update):
print('I received', update)
def message_poll_start():
try:
client = TelegramClient('phone', api_id, api_hash,
update_workers=1, spawn_read_thread=False)
client.connect()
client.add_update_handler(callback)
client.idle()
except TypeNotFoundError:
pass
def message_poll_start_thread(request):
t = threading.Thread(target=message_poll_start, args=(), kwargs={})
t.setDaemon(True)
t.start()
return HttpResponse("message polling started")
urls.py
urlpatterns = [
path('message_poll_start', messagemanager_views.message_poll_start_thread, name="message_poll_start"),
]
trace
[12/Jan/2018 11:24:38] "GET /messages/message_poll_start HTTP/1.1" 200 23
Exception in thread Thread-3:
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 "/home/teletogram/telethogram/messagemanager/views.py", line 123, in message_poll_start
client0.idle()
File "/home/teletogram/.env/lib/python3.5/site-packages/telethon/telegram_bare_client.py", line 825, in idle
signal(sig, self._signal_handler)
File "/usr/lib/python3.5/signal.py", line 47, in signal
handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
ValueError: signal only works in main thread
1) A python script runs in the main thread by default. If you spawn a new thread using threading.Thread, that will create a new thread which runs separately from the main one. When I began learning about threading I spent a lot of time reading before it started to click. The official threading docs are decent for basic functionality, and I like this tutorial for a deeper dive.
2) The internals of Telethon rely on asyncio. In asyncio each thread needs its own asynchronous event loop, and thus spawned threads need an explicitly created event loop. Like threading, asyncio is a large topic, some of which is covered in the Telethon docs.
Something like this should work:
import asyncio
def message_poll_start():
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
client = TelegramClient('phone', api_id, api_hash, loop=loop)
client.connect()
client.add_update_handler(callback)
client.idle()
except TypeNotFoundError:
pass