Avoid traceback after CTRL+C in async - python

I'm currently exploring async/await in python.
I'd like to start Pyrogram, then run the cronjob function. I created this code:
import asyncio
from pyrogram import Client, Filters, MessageHandler
import time
app = Client("pihome")
async def cronjob():
print("Cronjob started")
while True:
#print(f"time is {time.strftime('%X')}")
print(int(time.time()))
if int(time.strftime('%S')) == 10:
print("SECONDO 10") # Change this with checks in future
await asyncio.sleep(1)
async def main():
print(f"Starting {app.session_name}...\n")
# Pyrogram
await app.start()
print("Pyrogram started")
# Cronjob
loop = asyncio.new_event_loop()
loop.run_until_complete(loop.create_task(await cronjob()))
print("\nDone")
await Client.idle()
await app.stop()
print(f"\nStopping {app.session_name}...")
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
But when i want to stop it with Ctrl+C it gives me this traceback:
^CTraceback (most recent call last):
File "pihome.py", line 39, in <module>
asyncio.get_event_loop().run_until_complete(main())
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 603, in run_until_complete
self.run_forever()
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
self._run_once()
File "/usr/local/lib/python3.8/asyncio/base_events.py", line 1823, in _run_once
event_list = self._selector.select(timeout)
File "/usr/local/lib/python3.8/selectors.py", line 468, in select
fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt
How can I solve it? Also, try/except blocks seem not working

To run the application, it is better to use asyncio.run(main()). In my opinion, it’s more clear.
Replace loop = asyncio.new_event_loop() by loop = asyncio.get_event_loop(). Because using asyncio.new_event_loop() after asyncio.get_event_loop().run_until_complete(main()) you create a second event loop in main thread which is prohibited. Only one event loop per thread is permitted!
Remove await from this line:
loop.run_until_complete(loop.create_task(await cronjob()))
Because create_task need to Coroutine as the first argument but you pass it None

Related

Unable to Perform Graceful Shutdown of Asyncio App after a KeyboardInterrupt

I am having trouble letting my asyncio app shutdown gracefully when the user creates a KeyboardInterrupt by pressing Ctrl+C.
Why is it still not working properly despite using try... except KeyboardInterrupt? It still shows a traceback and something about a Unclosed AIOKafkaProducer.
import asyncio
from aiokafka import AIOKafkaProducer
class Foo:
def __init__(self):
self.producer = None
async def start(self):
self.producer = AIOKafkaProducer(bootstrap_servers="localhost:29092")
await self.producer.start()
self.loop_task = asyncio.create_task(self.loop())
await asyncio.gather(*[self.loop_task])
async def stop(self):
self.loop_task.cancel()
await self.producer.stop()
async def loop(self):
while True:
await asyncio.sleep(5)
async def main():
foo = Foo()
try:
await foo.start()
except KeyboardInterrupt:
print(
"Attempting graceful shutdown, press Ctrl+C+ again to exit...", flush=True
)
await foo.stop()
if __name__ == "__main__":
asyncio.run(main())
Error messages after pressing pressing Ctrl+C:
^CTraceback (most recent call last):
File "/home/x/test.py", line 37, in <module>
asyncio.run(main())
File "/opt/anaconda3/envs/testenv/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/opt/anaconda3/envs/testenv/lib/python3.9/asyncio/base_events.py", line 629, in run_until_complete
self.run_forever()
File "/opt/anaconda3/envs/testenv/lib/python3.9/asyncio/base_events.py", line 596, in run_forever
self._run_once()
File "/opt/anaconda3/envs/testenv/lib/python3.9/asyncio/base_events.py", line 1854, in _run_once
event_list = self._selector.select(timeout)
File "/opt/anaconda3/envs/testenv/lib/python3.9/selectors.py", line 469, in select
fd_event_list = self._selector.poll(timeout, max_ev)
KeyboardInterrupt
Unclosed AIOKafkaProducer
producer: <aiokafka.producer.producer.AIOKafkaProducer object at 0x7f643c0d0c40>
Adding try...except around main()
if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as e:
print("Exception:", e)
This gave the same traceback

Python websocket in a thread

I try to run a websocket in python in a thread. Here is the example I made:
import asyncio
import websockets
import threading
class websocket(threading.Thread):
def run(self):
asyncio.get_event_loop().run_until_complete(
websockets.serve(self.echo, 'localhost', 8765))
asyncio.get_event_loop().run_forever()
async def echo(websocket, path):
async for message in websocket:
await websocket.send(message)
ws = websocket()
ws.start()
I get that error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/home/xavier/dev/3003_mdt/mdt-robotarm/robotarm_server.py", line 7, in run
asyncio.get_event_loop().run_until_complete(
File "/usr/lib/python3.6/asyncio/events.py", line 694, in get_event_loop
return get_event_loop_policy().get_event_loop()
File "/usr/lib/python3.6/asyncio/events.py", line 602, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-1'.
Process finished with exit code 0
How can I run the websocket in a thread?
You've got the error because get_event_loop creates the loop automatically only in the main thread. Create the loop manually with new_event_loop like this:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(
websockets.serve(self.echo, "localhost", 8765)
)
loop.close()

Opposite of asyncio.to_thread

How do I run asynchronous function in blocking style? I am not allowed to modify signature of my mock function f1() and can't easy switch to async def and so can't use await expression.
async def cc2():
await asyncio.sleep(1)
await asyncio.to_thread(print, "qwerty")
def f1():
t = asyncio.create_task(cc2())
# wait until the task finished before returning
async def main():
f1() # typical call from many places of a program
asyncio.run(main())
I tried asyncio.get_running_loop().run_until_complete(t), but the hack does not work and I get the next error.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "<stdin>", line 2, in main
File "<stdin>", line 3, in f1
File "/usr/lib/python3.9/asyncio/base_events.py", line 618, in run_until_complete
self._check_running()
File "/usr/lib/python3.9/asyncio/base_events.py", line 578, in _check_running
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
How do I run asynchronous function in blocking style?
If you are in sync code, you can call it with asyncio.run(async_function()). If you are in async code, you can await async_function() or asyncio.create_task(async_function()), with the latter scheduling it to run in the background. You are not allowed to use asyncio.run() (or run_until_complete, even with a newly created event loop object) inside async code because it blocks and could halt the outer event loop.
But if you need it for testing purposes, you can always do something like:
# XXX dangerous - could block the current event loop
with concurrent.futures.ThreadPoolExecutor() as pool:
pool.submit(asyncio.run, async_function()).result()

RuntimeError when multithreading python "RuntimeError: There is no current event loop in thread 'Thread-1'."

I am trying to run multiple discord bots with the same script at once. The way I am doing this is going down a csv file, to get the bot token and bot name. I then start my script:
import time, csv, re, random, string, sys, os, asyncio
from datetime import datetime
from threading import Thread
import discord
from dotenv import load_dotenv
from discord_webhook import DiscordWebhook, DiscordEmbed
def main(bottoken, botname):
TOKEN = (bottoken)
client = discord.Client()
#client.event
async def on_ready():
print(f'{client.user.name} has connected to Discord!')
#client.event
async def on_message(message):
#do stuff
print('Do stuff')
client.run(TOKEN)
def runtask():
if __name__ == '__main__':
with open('botinfo.csv', 'r') as f:
reader = csv.reader(f, delimiter=',')
for i, row in enumerate(reader):
Thread(target = main, args=(row[0], row[1])).start()
if __name__ == '__main__':
runtask()
But then I get this error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 865, in run
self._target(*self._args, **self._kwargs)
File "/Users/alexforman/Documents/GitHub/bot-price-monitor/frontend_multithread.py", line 14, in main
client = discord.Client()
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/discord/client.py", line 216, in __init__
self.loop = asyncio.get_event_loop() if loop is None else loop
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/events.py", line 644, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-1'.
Exception in thread Thread-2:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 865, in run
self._target(*self._args, **self._kwargs)
File "/Users/alexforman/Documents/GitHub/bot-price-monitor/frontend_multithread.py", line 14, in main
client = discord.Client()
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/discord/client.py", line 216, in __init__
self.loop = asyncio.get_event_loop() if loop is None else loop
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/events.py", line 644, in get_event_loop
% threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Thread-2'.
Does anyone have any ideas on how I could fix it?
You don't need to start a separate thread for each bot, the asyncio event loop is perfectly capable of serving all bots at once. (That's the kind of thing event loops are designed for.)
You just need to start all the bots as coroutines of the same asyncio event loop:
async def run_client(bottoken, botname):
TOKEN = (bottoken)
client = discord.Client()
#client.event
async def on_ready():
print(f'{client.user.name} has connected to Discord!')
#client.event
async def on_message(message):
#do stuff
print('Do stuff')
# can't use client.run() because it would block the event loop
# but we can use client.start(), which is a coroutine that does
# the same thing (and which is internally called by client.run)
await client.start(TOKEN)
async def main():
coros = []
with open('botinfo.csv', 'r') as f:
reader = csv.reader(f, delimiter=',')
for i, row in enumerate(reader):
coros.append(run_client(row[0], row[1]))
await asyncio.gather(*coros)
if __name__ == '__main__':
asyncio.run(main())

Python RuntimeError: This event loop is already running

It is a very common issue, but I cannot actually find a proper answer to it. I have the following code to connect to a server through a websocket and I want to keep it alive and keep listening to messages it sends like below:
import asyncio
import websockets
import nest_asyncio
nest_asyncio.apply()
async def listen_for_message(websocket):
while True:
await asyncio.sleep(0)
message = await websocket.recv()
print(message)
async def connect_to_dealer():
websocket = await websockets.connect(websocketadress)
hello_message = await websocket.recv()
print(hello_message)
async def my_app():
websocket = await connect_to_dealer()
asyncio.ensure_future(listen_for_message(websocket))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(my_app())
loop.run_forever()
And it raises the error:
File "\untitled0.py", line 71, in <module>
loop.run_forever()
File "\Anaconda3\lib\asyncio\base_events.py", line 525, in run_forever
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
And without the
import nest_asyncio
nest_asyncio.apply()
I get:
File "\untitled0.py", line 70, in <module>
loop.run_until_complete(my_app())
File "\Anaconda3\lib\asyncio\base_events.py", line 570, in run_until_complete
self.run_forever()
File "\Anaconda3\lib\asyncio\base_events.py", line 525, in run_forever
raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
I still do not understand why it does this.
loop.run_until_complete() already runs your loop 'forever'.
Instead of firing of your listen_for_message() awaitable as a task, just await on it. That then runs forever, because listen_for_message() itself never returns:
async def my_app():
websocket = await connect_to_dealer()
await listen_for_message(websocket)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(my_app())
Note that your connect_to_dealer() function doesn't return the websocket; that's probably an oversight you want to correct:
async def connect_to_dealer():
return await websockets.connect(websocketadress)
I removed the hello_message = await websocket.recv() / print(hello_message) lines there because listen_for_message() will already receive messages and print them.

Categories

Resources