How to "pause" a script until asyncio is closed - python

I have a function that calls a class, after the class has been initilized it will continue down the function, as it should. However i want to pause after the class has been initilized until the asyncio is closed from inside the class.
Something like this:
from PyHook3 import HookManager
import asyncio
class Keystroke_Watcher(object):
def __init__(self, event_loop):
self.event_loop = event_loop
self.hm = HookManager()
self.hm.KeyDown = self.on_keyboard_event
self.hm.HookKeyboard()
def on_keyboard_event(self, event):
if event.KeyID == 65: # A
self.shutdown()
return True
def shutdown(self):
self.hm.UnhookKeyboard()
self.event_loop.stop()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
watcher = Keystroke_Watcher(loop)
loop.run_forever()
print("Done!")
The idea is the "main" code shouldn't continue until after the "A" key has been pressed. However with this current code it will freeze all keyboard actions, and it won't stop even after A is pressed.
Am I using the wrong asyncio loop?
Doesn't it work well with classes?
What would be the best solution to this problem? (doesn't necessarily to involve asyncio in solution)
Note: I know that Qt has something that does exactly this, however i don't feel like including the entire qt library. QEventLoop().exec() to start it and QEventLoop().exit() to end it.

Related

The most proper way to stop event loop after asyncio.run has been called?

Considering the following code:
import asyncio
async def main() -> None:
await asyncio.sleep(2**256)
if __name__ == '__main__':
asyncio.run(main())
What is the most proper way to terminate coroutine main, after it has been called by asyncio.run? When I invoked script and pressed CTRL + C, I saw an ugly traceback.
As I can see from the source code, asyncio.run does a lot of machinery behind the scenes, so I would like to continue using it.
You probably should handle the SIGINT signal.
import asyncio
import functools
import signal
async def main() -> None:
await asyncio.sleep(2**256)
def handler(loop):
...
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, functools.partial(handler, loop=loop))
loop.run_until_complete(main())
The question is how the handler should look like? If you want just close program without exception, use sys.exit()
def handler(loop):
sys.exit()
However to close everything gracefully, you need to finish every task and stop the loop. See this topic for more insight.

How do I exit my python3 application cleanly from asyncio event loop run_forever() when user clicks tkinter root window close box?

I'm trying to make a python3 application for my Raspberry Pi 4B and I have the tkinter windows working fine, but need to add asynchronous handling to allow tkinter widgets to respond while processing asynchronous actions initiated by the window's widgets.
The test code is using asyncio and tkinter. However, without root.mainloop(), since asyncio loop.run_forever() is called at the end instead. The idea is that when the user clicks the main window's close box, RequestQuit() gets called to set the quitRequested flag and then when control returns to the event loop, root.after_idle(AfterIdle) would cause AfterIdle to be called, where the flag is checked and if true, the event loop is stopped, or that failing, the app is killed with exit(0).
The loop WM_DELETE_WINDOW protocol coroutine RequestQuit is somehow not getting called when the user clicks the main window close box, so the AfterIdle coroutine never gets the flag to quit and I have to kill the app by quitting XQuartz.
I'm using ssh via Terminal on MacOS X Big Sur 11.5.2, connected to a Raspberry Pi 4B with Python 3.7.3.
What have I missed here?
(I haven't included the widgets or their handlers or the asynchronous processing here, for brevity, since they aren't part of the problem at hand.)
from tkinter import *
from tkinter import messagebox
import aiotkinter
import asyncio
afterIdleProcessingIntervalMsec = 500 # Adjust for UI responsiveness here.
busyProcessing = False
quitRequested = False
def RequestQuit():
global quitRequested
global busyProcessing
if busyProcessing:
answer = messagebox.askquestion('Exit application', 'Do you really want to abort the ongoing processing?', icon='warning')
if answer == 'yes':
quitRequested = True
def AfterIdle():
global quitRequested
global loop
global root
if not quitRequested:
root.after(afterIdleProcessingIntervalMsec, AfterIdle)
else:
print("Destroying GUI at: ", time.time())
try:
loop.stop()
root.destroy()
except:
exit(0)
if __name__ == '__main__':
global root
global loop
asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())
loop = asyncio.get_event_loop()
root = Tk()
root.protocol("WM_DELETE_WINDOW", RequestQuit)
root.after_idle(AfterIdle)
# Create and pack widgets here.
loop.run_forever()
The reason why your program doesn't work is that there is no Tk event loop, or its equivalent. Without it, Tk will not process events; no Tk callback functions will run. So your program doesn't respond to the WM_DELETE_WINDOW event, or any other.
Fortunately Tk can be used to perform the equivalent of an event loop as an asyncio.Task, and it's not even difficult. The basic concept is to write a function like this, where "w" is any tk widget:
async def new_tk_loop():
while some_boolean:
w.update()
await asyncio.sleep(sleep_interval_in_seconds)
This function should be created as an asyncio.Task when you are ready to start processing tk events, and should continue to run until you are ready to stop doing that.
Here is a class, TkPod, that I use as the basic foundation of any Tk + asyncio program. There is also a trivial little demo program, illustrating how to close the Tk loop from another Task. If you click the "X" before 5 seconds pass, the program will close immediately by exiting the mainloop function. After 5 seconds the program will close by cancelling the mainloop task.
I use a default sleep interval of 0.05 seconds, which seems to work pretty well.
When exiting such a program there are a few things to think about.
When you click on the "X" button on the main window, the object sets its app_closing variable to false. If you need to do some other clean-up, you can subclass Tk and over-ride the method close_app.
Exiting the mainloop doesn't call the destroy function. If you need to do that, you must do it separately. The class is a context manager, so you can make sure that destroy is called using a with block.
Like any asyncio Task, mainloop can be cancelled. If you do that, you need to catch that exception to avoid a traceback.
#! python3.8
import asyncio
import tkinter as tk
class TkPod(tk.Tk):
def __init__(self, sleep_interval=0.05):
self.sleep_interval = sleep_interval
self.app_closing = False
self.loop = asyncio.get_event_loop()
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.close_app)
# Globally suppress the Tk menu tear-off feature
# In the following line, "*tearOff" works as documented
# while "*tearoff" does not.
self.option_add("*tearOff", 0)
def __enter__(self):
return self
def __exit__(self, *_x):
self.destroy()
def close_app(self):
self.app_closing = True
# I don't know what the argument n is for.
# I include it here because pylint complains otherwise.
async def mainloop(self, _n=0):
while not self.app_closing:
self.update()
await asyncio.sleep(self.sleep_interval)
async def main():
async def die_in5s(t):
await asyncio.sleep(5.0)
t.cancel()
print("It's over...")
with TkPod() as root:
label = tk.Label(root, text="Hello")
label.grid()
t = asyncio.create_task(root.mainloop())
asyncio.create_task(die_in5s(t))
try:
await t
except asyncio.CancelledError:
pass
if __name__ == "__main__":
asyncio.run(main())

await asyncio.sleep(1) not working in python

My code execution does not reach the print statement: print("I want to display after MyClass has started")
Why is this? I thought the purpose of await asyncio.sleep() is to unblock execution of code so that subsequent lines of code can run. Is that not the case?
import asyncio
class MyClass:
def __init__(self):
self.input = False
asyncio.run(self.start())
print("I want to display after MyClass has started") #This line is never reached.
async def start(self):
while True:
print("Changing state...")
if self.input:
print("I am on.")
break
await asyncio.sleep(1)
m = MyClass()
m.input = True #This line is never reached! Why?
print("I want to display after MyClass is started")
When I execute, it keeps printing "Changing state...". Even when I ctrl+c to quit, the execution continues as shown below. How can I properly terminate the execution? Sorry, I am new to python.
EDIT:
I appreciate the common use of asyncio is for running two or more separate functions asynchronously. However, my class is one which will be responding to changes in its state. For example, I intend to write code in the setters to do stuff when the class objects attributes change -WHILE still having a while True event loop running in the background. Is there not any way to permit this? I have tried running the event loop in it's own thread. However, that thread then dominates and the class objects response times run into several seconds. This may be due to the GIL (Global Interpreter Lock) which we can do nothing about. I have also tried using multiprocessing, but then I lose access to the properties and methods of the object as parallel process run in their own memory spaces.
In the init method of MyClass you invoke asyncio.run() - this method will execute the required routine until that routine terminates. In your case, since the main method includes a while True loop, it will never terminate.
Here is a slight modification of your code that perhaps shows the concurrency effect you're after -
import asyncio
class MyClass:
def __init__(self):
self.input = False
asyncio.run(self.main())
print("I want to display after MyClass has been initialized.") # This line is never reached.
async def main(self):
work1 = self.work1()
work2 = self.work2()
await asyncio.gather(work1, work2)
async def work1(self):
for idx in range(5):
print('doing some work 1...')
await asyncio.sleep(1)
async def work2(self):
for idx in range(5):
print('doing some work 2...')
await asyncio.sleep(1)
m = MyClass()
print("I want to display after MyClass is terminated")

How to make my code stopable? (Not killing/interrupting)

I'm asking this question in a more broad spectrum because I'm not facing this specific issue right now, but I'm wondering how to do it in the future.
If I have a long running python script, that is supposed to do something all the time (could be a infine loop, if that helps). The code is started by running python main.py command on a terminal.
The code doesn't have an ending, so there will be no sys.exit().
I don't want to use KeyboardInterrupt and I don't want to kill the task. Because those options are abrupt, and you can't predict precisely at what point you are stoping the code.
Is there a way to 'softly' terminate the code when I eventually decide to fo it? For example using another command, preparing a class or running another script?
What would be the best practice for this?
PS.: Please, bear in mind that I'm a novice coder.
EDIT:
I'm adding some generic code, in order to make my question clearer.
import time,csv
import GenericAPI
class GenericDataCollector:
def __init__(self):
self.generic_api = GenericAPI()
def collect_data(self):
while True: #Maybe this could be a var that is changed from outside of the class?
data = self.generic_api.fetch_data() #Returns a JSON with some data
self.write_on_csv(data)
time.sleep(1)
def write_on_csv(self, data):
with open('file.csv','wt') as f:
writer = csv.writer(f)
writer.writerow(data)
def run():
obj = GenericDataCollector()
obj.collect_data()
if __name__ == "__main__":
run()
In this particular case, the class is collecting data from some generic API (that comes in JSON) and writing it in a csv file, in a infinite loop. How could I code a way (method?) to stop it (when called uppon, so unexpected), without abruptly interrupting (Ctrl+C or killing task).
I would recommend use the signal module. This allows you to handle signal interrupts (SIGINT) and clean up the program before your exit. Take the following code for example:
import signal
running = True
def handle(a, b):
global running
running = False
# catch the SIGINT signal and call handle() when the process
# receives it
signal.signal(signal.SIGINT, handle)
# your code here
while running:
pass
You can still exit with a Ctrl+C, but what you put in the while loop will not be cut off half way.
Based on #Calder White, how about this (not tested):
import signal
import time,csv
import GenericAPI
class GenericDataCollector:
def __init__(self):
self.generic_api = GenericAPI()
self.cont = True
def collect_data(self):
while self.cont:
signal.signal(signal.SIGINT, self.handle)
data = self.generic_api.fetch_data() #Returns a JSON with some data
self.write_on_csv(data)
time.sleep(1)
def handle(self):
self.cont = False
def write_on_csv(self, data):
with open('file.csv','wt') as f:
writer = csv.writer(f)
writer.writerow(data)
def run():
obj = GenericDataCollector()
obj.collect_data()
if __name__ == "__main__":
run()

Threading with Bottle.py Server

I'm having an issue with threading that I can't solve in any way I've tried. I searched in StackOverflow too, but all I could find was cases that didn't apply to me, or explanations that I didn't understand.
I'm trying to build an app with BottlePy, and one of the features I want requires a function to run in background. For this, I'm trying to make it run in a thread. However, when I start the thread, it runs twice.
I've read in some places that it would be possible to check if the function was in the main script or in a module using if __name__ == '__main__':, however I'm not able to do this, since __name__ is always returning the name of the module.
Below is an example of what I'm doing right now.
The main script:
# main.py
from MyClass import *
from bottle import *
arg = something
myObject = Myclass(arg1)
app = Bottle()
app.run('''bottle args''')
The class:
# MyClass.py
import threading
import time
class MyClass:
def check_list(self, theList, arg1):
a_list = something()
time.sleep(5)
self.check_list(a_list, arg1)
def __init__(self, arg1):
if __name__ == '__main__':
self.a_list = arg.returnAList()
t = threading.Thread(target=self.check_list, args=(a_list, arg1))
So what I intend here is to have check_list running in a thread all the time, doing something and waiting some seconds to run again. All this so I can have the list updated, and be able to read it with the main script.
Can you explain to me what I'm doing wrong, why the thread is running twice, and how can I avoid this?
This works fine:
import threading
import time
class MyClass:
def check_list(self, theList, arg1):
keep_going=True
while keep_going:
print("check list")
#do stuff
time.sleep(1)
def __init__(self, arg1):
self.a_list = ["1","2"]
t = threading.Thread(target=self.check_list, args=(self.a_list, arg1))
t.start()
myObject = MyClass("something")
Figured out what was wrong thanks to the user Weeble's comment. When he said 'something is causing your main.py to run twice' I remembered that Bottle has an argument that is called 'reloader'. When set to True, this will make the application load twice, and thus the thread creation is run twice as well.

Categories

Resources