• asyncio and tkinter (was: What should go to stdout/stderr and why Pytho

    From Stefan Ram@21:1/5 to Thomas Passin on Fri Jan 6 00:52:40 2023
    Thomas Passin <list1@tompassin.net> writes:
    On 1/5/2023 4:24 PM, Stefan Ram wrote:
    You often can replace threads in tkinter by coroutines using
    asyncio when you write a replacement for the mainloop of
    tkinter that uses asyncio. Now, try to read only the official
    documentation of asyncio and tkinter and figure out only from
    this how to get such a program to work!
    Cool! Can we have a link, please?

    I do not post links, but tried to create a minimal example app.

    import asyncio
    import logging
    import tkinter

    # This program was tested on Windows, it is experimental.
    # It should open a tkinter root window.
    # Ctrl-E should create a task. You should be able to create
    # a new task even while one task is already running.
    # Each task will end after about 10 seconds.
    # Ctrl-X should exit the process.
    # Or, use the menu "Action".

    # I (Stefan Ram) designed and wrote the program, but especially
    # the code in "app_async_mainloop" and "terminate_tasks" was
    # written following educational material from the World-Wide Web.

    class app_class( tkinter.Tk ):
    def __init__( self, *args, **kwargs ):
    self.is_running = 1_000_000
    super().__init__( *args, **kwargs )
    root = self
    root.protocol( "WM_DELETE_WINDOW", self.app_exit_macro )
    async def app_example_task( self, example_label ):
    try:
    for i in range( 10 ):
    example_label[ 'text' ]=str( i )
    await asyncio.sleep( 1 )
    except asyncio.CancelledError:
    pass
    example_label.destroy()
    def app_example_macro( self ):
    root = self
    example_label=tkinter.Label( root )
    example_label.pack()
    asyncio.get_running_loop().create_task\
    ( self.app_example_task( example_label ))
    root = self
    def terminate_tasks( self ):
    loop = asyncio.get_running_loop()
    pending = asyncio.all_tasks( loop=loop )
    label_tasks = []
    for task in pending:
    if 'app_example_task' in str( task ):
    label_tasks.append( task )
    task.cancel()
    group = asyncio.gather( *label_tasks, return_exceptions=True )
    try:
    loop.run_until_complete( group )
    except AssertionError:
    # ignoring an assertion error on Windows I do not understand
    pass
    def app_exit_macro( self ):
    self.terminate_tasks()
    self.is_running = 99
    root = self
    root.destroy()
    def app_add_basemenu( self, example=False ):
    root = self
    menubar = tkinter.Menu( root )
    menu = tkinter.Menu( menubar, tearoff=0 )
    name = "Actions"; menu = tkinter.Menu( menubar, tearoff=0 )
    if example:
    text = "Example";
    callback = self.app_example_macro; menu.add_command\
    ( label=text, underline=0, command=callback,
    accelerator="Control-e" );
    root.bind\
    ( "<Control-e>", lambda event, callback=callback:callback() )
    text = "Exit";
    callback = self.app_exit_macro
    menu.add_command\
    ( label=text, underline=1,
    command=callback, accelerator="Control-x" )
    root.bind\
    ( "<Control-x>", lambda event,callback=callback: callback() )
    menubar.add_cascade( label=name, underline=0, menu=menu )
    root.config( menu=menubar )
    async def app_async_mainloop( self ):
    root = self
    root.willdispatch()
    root.tk.dooneevent( tkinter._tkinter.DONT_WAIT )
    while self.is_running > 0:
    if self.is_running < 1_000_000: self.is_running -= 1
    try:
    await asyncio.sleep\
    ( tkinter._tkinter.getbusywaitinterval() / 10_000 )
    root.tk.dooneevent( tkinter._tkinter.DONT_WAIT )
    root.update()
    except tkinter.TclError:
    break
    async def app_async_main( self ):
    try:
    await self.app_async_mainloop()
    except asyncio.CancelledError:
    logging.debug( f'asyncio.CancelledError' )
    def app_async_run( self ):
    asyncio.run( self.app_async_main() )

    app = app_class()
    app.app_add_basemenu(example=True)
    app.app_async_run()

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Thomas Passin@21:1/5 to Stefan Ram on Thu Jan 5 22:44:40 2023
    On 1/5/2023 7:52 PM, Stefan Ram wrote:
    Thomas Passin <list1@tompassin.net> writes:
    On 1/5/2023 4:24 PM, Stefan Ram wrote:
    You often can replace threads in tkinter by coroutines using
    asyncio when you write a replacement for the mainloop of
    tkinter that uses asyncio. Now, try to read only the official
    documentation of asyncio and tkinter and figure out only from
    this how to get such a program to work!
    Cool! Can we have a link, please?

    I do not post links, but tried to create a minimal example app.

    Thanks! That's not exactly obvious, is it? Congratulations on getting
    it working.

    import asyncio
    import logging
    import tkinter

    # This program was tested on Windows, it is experimental.
    # It should open a tkinter root window.
    # Ctrl-E should create a task. You should be able to create
    # a new task even while one task is already running.
    # Each task will end after about 10 seconds.
    # Ctrl-X should exit the process.
    # Or, use the menu "Action".

    # I (Stefan Ram) designed and wrote the program, but especially
    # the code in "app_async_mainloop" and "terminate_tasks" was
    # written following educational material from the World-Wide Web.

    class app_class( tkinter.Tk ):
    def __init__( self, *args, **kwargs ):
    self.is_running = 1_000_000
    super().__init__( *args, **kwargs )
    root = self
    root.protocol( "WM_DELETE_WINDOW", self.app_exit_macro )
    async def app_example_task( self, example_label ):
    try:
    for i in range( 10 ):
    example_label[ 'text' ]=str( i )
    await asyncio.sleep( 1 )
    except asyncio.CancelledError:
    pass
    example_label.destroy()
    def app_example_macro( self ):
    root = self
    example_label=tkinter.Label( root )
    example_label.pack()
    asyncio.get_running_loop().create_task\
    ( self.app_example_task( example_label ))
    root = self
    def terminate_tasks( self ):
    loop = asyncio.get_running_loop()
    pending = asyncio.all_tasks( loop=loop )
    label_tasks = []
    for task in pending:
    if 'app_example_task' in str( task ):
    label_tasks.append( task )
    task.cancel()
    group = asyncio.gather( *label_tasks, return_exceptions=True )
    try:
    loop.run_until_complete( group )
    except AssertionError:
    # ignoring an assertion error on Windows I do not understand
    pass
    def app_exit_macro( self ):
    self.terminate_tasks()
    self.is_running = 99
    root = self
    root.destroy()
    def app_add_basemenu( self, example=False ):
    root = self
    menubar = tkinter.Menu( root )
    menu = tkinter.Menu( menubar, tearoff=0 )
    name = "Actions"; menu = tkinter.Menu( menubar, tearoff=0 )
    if example:
    text = "Example";
    callback = self.app_example_macro; menu.add_command\
    ( label=text, underline=0, command=callback,
    accelerator="Control-e" );
    root.bind\
    ( "<Control-e>", lambda event, callback=callback:callback() )
    text = "Exit";
    callback = self.app_exit_macro
    menu.add_command\
    ( label=text, underline=1,
    command=callback, accelerator="Control-x" )
    root.bind\
    ( "<Control-x>", lambda event,callback=callback: callback() )
    menubar.add_cascade( label=name, underline=0, menu=menu )
    root.config( menu=menubar )
    async def app_async_mainloop( self ):
    root = self
    root.willdispatch()
    root.tk.dooneevent( tkinter._tkinter.DONT_WAIT )
    while self.is_running > 0:
    if self.is_running < 1_000_000: self.is_running -= 1
    try:
    await asyncio.sleep\
    ( tkinter._tkinter.getbusywaitinterval() / 10_000 )
    root.tk.dooneevent( tkinter._tkinter.DONT_WAIT )
    root.update()
    except tkinter.TclError:
    break
    async def app_async_main( self ):
    try:
    await self.app_async_mainloop()
    except asyncio.CancelledError:
    logging.debug( f'asyncio.CancelledError' )
    def app_async_run( self ):
    asyncio.run( self.app_async_main() )

    app = app_class()
    app.app_add_basemenu(example=True)
    app.app_async_run()




    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)