Below is my script on using asyncio
to operate with tkinter
and its ttk.Progressbar()
widget. I derived it after referring to these references (1, 2, 3, 4, 5). I got the progessbar widget to work. But I can't seem to be able to cancel(stop) the asyncio
Task object responsible for updating tkinter.Tk()
that I have created to replace the usual Tk()
event mainloop()
. As such, I dont' see a command prompt or >>>
prompt after the Tk() window is destroyed. How can I or should I terminate the task object root.update_task
? I am using python 3.6. I was hoping loop.shutdown_asyncgens()
could end the cancelled task but it did not. Why did it not work? Thank you.
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox as tkMessageBox
import time
import asyncio
INTERVAL = 0.05 #seconds
class App(ttk.Frame):
def __init__( self, master, loop, interval=0.05, *args, **kw ):
super().__init__( master,style='App.TFrame')
self.master = master
self.loop = loop
self._set_style()
self._create_widgets()
def _set_style( self ):
print( '\ndef _set_style( self ):' )
self.style = ttk.Style()
self.style.configure( 'App.TFrame', background='pink')
self.style.configure( 'sp.TFrame', background='light green')
def _create_widgets( self ):
print( '\ndef _create_widgets( self ):' )
self.sp_frame = ttk.Frame( self, style='sp.TFrame' )
self.sp_frame.grid(row=0, column=0)
#sp_frame widgets
self.sp_label1 = ttk.Label( self.sp_frame, text='SP(s):')
self.sp_combox = ttk.Combobox(
self.sp_frame, state="readonly", values=['a','b','c'] )
self.sp_combox.bind('<<ComboboxSelected>>', self._connect_esp)
self.sp_pbar = ttk.Progressbar( self.sp_frame, length=200,
mode='indeterminate',
orient=tk.HORIZONTAL, )
self.sp_label1.grid( row=0, column=0 )
self.sp_combox.grid( row=0, column=1, padx=[10,0] )
self.sp_pbar.grid( row=1, column=0, columnspan=2, sticky='ew' )
def _connect_esp( self, event):
print( '\ndef connect_esp( self, event ):' )
async def dojob( loop, start_time, duration=1 ):
print( '\nasync def dojob( loop, end_time):' )
while True:
duration = 5 #seconds
t = loop.time()
delta = t - start_time
print( 'wait time = {}'.format( delta ) )
if delta >= duration:
break
await asyncio.sleep( 1 )
return True
async def trackjob( loop ):
print( '\nasync def trackjob( loop ):' )
print( 'Job: STARTED' )
start_time = loop.time()
self.sp_pbar.start( 50 )
self.sp_pbar.update_idletasks()
result = await dojob( loop, start_time )
print( 'result = ', result, type(result) )
if result:
self.sp_pbar.stop()
self.sp_pbar.update_idletasks()
print( 'Job: ENDED' )
return True
return False
try:
future = self.loop.create_task( trackjob( self.loop ) )
print( 'future = ', future, type(future))
except syncio.CancelledError as err:
print( '_connect_esp(): future is cancelled.' )
raise
except asyncio.InvalidStateError as err:
print( '_connect_esp(): The operation is not allowed in this state..' )
raise
except asyncio.TimeoutError as err:
print( '_connect_esp(): The operation exceeded the given deadline..' )
raise
except Exception:
raise
async def tk_update( root, interval=INTERVAL ):
print( '\nasync def tk_update( interval ):' )
try:
while True:
root.update() #tk update
await asyncio.sleep( interval )
except tk.TclError as err:
print( '\nasync def tk_update( self, interval ):' )
if "application has been destroyed" not in err.args[0]:
raise
except asyncio.CancelledError as err:
print( '\nasync def tk_update( self, interval ):' )
print('Request to cancel tk_update_task received but may not be done.')
await asyncio.sleep( interval )
print( '\nasync def tk_update( interval ):' )
print( '0 Cancelled = ', root.update_task.cancelled() )
print('END of def tk_update')
def ask_quit( root, interval=INTERVAL ):
'''Confirmation to quit application.'''
print( '\ndef ask_quit( self ):' )
task=root.update_task
if tkMessageBox.askokcancel( "Quit","Quit?" ):
task.cancel()
print( '1 Cancelled = ', task.cancelled() )
root.destroy() #Destroy the Tk Window instance.
print( '2 Cancelled = ', task.cancelled() )
def main():
loop = asyncio.get_event_loop()
root = tk.Tk()
root.geometry('300x100+0+24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
root.update_task = loop.create_task( tk_update( root ) )
app = App( root, loop )
app.grid(row=0, column=0, sticky='nsew')
#root.mainloop() #DO NOT IMPLEMENT; this is replaced by running
# tk's update() method in a asyncio loop called loop.
# See tk_update() method and root.update_task.
#Tell Tk window instance what to do before it is destroyed.
root.protocol("WM_DELETE_WINDOW",
lambda :ask_quit( root ) )
try:
loop.run_forever()
print('after loop.run_forever()')
finally:
loop.run_until_complete( loop.shutdown_asyncgens() )
loop.close()
print( 'Is Loop closed = ', loop.isclosed() )
if __name__ == '__main__':
main()