Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle GUI, such as pysimplegui, from going into 'Not Responding' when running in an infinite loop?

Tags:

python

I have code that is running through PySimpleGUI that executes as expected but the problem is the GUI appears as not responding when running through the code. I want it to appear as its running without showing 'Not Responding' in the header of the window.

I believe this is because of having an Infinite Loop of While True: but this is for the purpose of PySimpleGUI to run.

Here is a snippet of my loop:

while True:
    event, values = window.Read()
    #print(event, values)
    some_functions()
window.close()

I expect to have the window run normal when a button is pressed without going into not responding after few seconds, it is working as in running the code as expected but I want to hide the appearance of not responding somehow.

like image 931
Omar Ghafri Avatar asked Jan 26 '23 22:01

Omar Ghafri


1 Answers

[ EDIT Nov 2021 ]

There's a rather large problem with using StackOverflow to get answers for projects that are ACTIVE and thus constantly evolving, hopefully in ways that improve the package.

Stack Overflow answers, at best, offer solutions in a snapshot in time.

There's the awful problem that even at the time written, some of the answers are not written by experts in that package.

When an expert answers the question if the package evolves, the answer can become both not applicable or worst sends the user down a path that is doomed to fail, or wastes time solving a problem that's been solved in a more elegant way.

In this particular situation, PySimpleGUI has evolved to include a simple wrapper for the Thread standard library calls. Why? Because someone entirely new to programming that hits this long-operation problem won't have the ability to grasp threading, but yet will still encounter this very real problem, perhaps on their very first GUI.

In Aug 2021 a new method was added to the Window object.... Window.perform_long_operation which removes the burden entirely from a new user.

perform_long_operation is in the PySimpleGUI Cookbook, in the eCookbook, in demo programs and can remove this significant roadblock for a newcomer and enable them to get on with solving their original problem rather than getting mired in a topic they are not prepared to tackle in their first 2 weeks of programming, ever.

I don't have the ability to scour StackOverflow for my previous answers and update them. I urge PySimpleGUI users to follow the support steps outlined in the documentation. It's one reason I rarely come here and answer questions. Support is provided elsewhere.

On to the now outdated answer....

The problem isn't the loop. The problem is your functions that you're calling after you call Read. If those functions didn't take so long, you wouldn't see the Not Responding message. Take out that call and your problem goes away, right? But, of course you can't take out that call because that's the stuff you need to do in your GUI.

There's a Demo Program written for this very purpose. Any time you're taking so much "time" that the operating system is complaining it means you're taking too much.

It's a classic GUI problem that exists in all GUI SDKs. In the PySimpleGUI Event Loop or in callbacks from other frameworks, you'll get these kinds of OS warnings that your program has stopped working.

Because PySimpleGUI is tkinter based, you can't simply spin out the GUI as another thread. tkinter doesn't like to be anything but the main thread. So, it's your portion of the work that needs to be run as a separate thread so that the GUI continues to be given CPU time and the OS will thus stop complaining.

Last time I posted only a link on SO I was scolded, so I'm going to paste in the code here for the demo program. You can use this as a design pattern for your code. As mentioned in the comments you can run it online to see how it works by running the code on repl.it.

import queue
import threading
import time

# This program has been tested on all flavors of PySimpleGUI and it works with no problems at all
# To try something other than tkinter version, just comment out the first import and uncomment the one you want
import PySimpleGUI as sg
# import PySimpleGUIQt as sg
# import PySimpleGUIWx as sg
# import PySimpleGUIWeb as sg

"""
    DESIGN PATTERN - Multithreaded Long Tasks GUI
    Presents one method for running long-running operations in a PySimpleGUI environment.
    The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
    The "long work" is contained in the thread that is being started.

    A queue.Queue is used by the threads to communicate with main GUI code
    The PySimpleGUI code is structured just like a typical PySimpleGUI program.  A layout defined,
        a Window is created, and an event loop is executed.
    What's different is that within this otherwise normal PySimpleGUI Event Loop, there is a check for items
        in the Queue.  If there are items found, process them by making GUI changes, and continue.

    This design pattern works for all of the flavors of PySimpleGUI including the Web and also repl.it
    You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
"""


def long_operation_thread(seconds, gui_queue):
    """
    A worker thread that communicates with the GUI through a queue
    This thread can block for as long as it wants and the GUI will not be affected
    :param seconds: (int) How long to sleep, the ultimate blocking call
    :param gui_queue: (queue.Queue) Queue to communicate back to GUI that task is completed
    :return:
    """
    print('Starting thread - will sleep for {} seconds'.format(seconds))
    time.sleep(seconds)                  # sleep for a while
    gui_queue.put('** Done **')  # put a message into queue for GUI


######   ##     ## ####
##    ##  ##     ##  ##
##        ##     ##  ##
##   #### ##     ##  ##
##    ##  ##     ##  ##
##    ##  ##     ##  ##
######    #######  ####

def the_gui():
    """
    Starts and executes the GUI
    Reads data from a Queue and displays the data to the window
    Returns when the user exits / closes the window
    """

    gui_queue = queue.Queue()  # queue used to communicate between the gui and the threads

    layout = [[sg.Text('Long task to perform example')],
              [sg.Output(size=(70, 12))],
              [sg.Text('Number of seconds your task will take'),sg.Input(key='_SECONDS_', size=(5,1)), sg.Button('Do Long Task', bind_return_key=True)],
              [sg.Button('Click Me'), sg.Button('Exit')], ]

    window = sg.Window('Multithreaded Window').Layout(layout)

    # --------------------- EVENT LOOP ---------------------
    while True:
        event, values = window.Read(timeout=100)       # wait for up to 100 ms for a GUI event
        if event is None or event == 'Exit':
            break
        elif event.startswith('Do'):
            try:
                seconds = int(values['_SECONDS_'])
                print('Starting thread to do long work....sending value of {} seconds'.format(seconds))
                threading.Thread(target=long_operation_thread, args=(seconds , gui_queue,), daemon=True).start()
            except Exception as e:
                print('Error starting work thread. Did you input a valid # of seconds? You entered: %s' % values['_SECONDS_'])
        elif event == 'Click Me':
            print('Your GUI is alive and well')
        # --------------- Check for incoming messages from threads  ---------------
        try:
            message = gui_queue.get_nowait()
        except queue.Empty:             # get_nowait() will get exception when Queue is empty
            message = None              # break from the loop if no more messages are queued up

        # if message received from queue, display the message in the Window
        if message:
            print('Got a message back from the thread: ', message)

    # if user exits the window, then close the window and exit the GUI func
    window.Close()


##     ##    ###    #### ##    ##
###   ###   ## ##    ##  ###   ##
#### ####  ##   ##   ##  ####  ##
## ### ## ##     ##  ##  ## ## ##
##     ## #########  ##  ##  ####
##     ## ##     ##  ##  ##   ###
##     ## ##     ## #### ##    ##

if __name__ == '__main__':
    the_gui()
    print('Exiting Program')
like image 142
Mike from PSG Avatar answered Jan 28 '23 16:01

Mike from PSG