From 6acdac5a96fa3a1256de3b34cf4547f37990c19d Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Wed, 15 May 2019 08:56:56 -0400 Subject: [PATCH] Better commenting and message passing with "Work ID" checks --- DemoPrograms/Demo_Threaded_Work.py | 107 +++++++++++------------------ 1 file changed, 39 insertions(+), 68 deletions(-) diff --git a/DemoPrograms/Demo_Threaded_Work.py b/DemoPrograms/Demo_Threaded_Work.py index 8c38b459..af290345 100644 --- a/DemoPrograms/Demo_Threaded_Work.py +++ b/DemoPrograms/Demo_Threaded_Work.py @@ -5,6 +5,11 @@ import time import PySimpleGUI as sg """ + You want to look for 3 points in this code, marked with comment "LOCATION X". + 1. Where you put your call that takes a long time + 2. Where the trigger to make the call takes place in the event loop + 3. Where the completion of the call is indicated in the event loop + Demo on how to add a long-running item to your PySimpleGUI Event Loop If you want to do something that takes a long time, and you do it in the main event loop, you'll quickly begin to see messages from windows that your @@ -17,109 +22,75 @@ import PySimpleGUI as sg is spun off, allowed to work, and then gets back to the GUI when it's done working on that task. - If you have multiple long tasks to run, then you'll want a more sophisticated - format to your messages going back to the GUI so you'll know which task finished + Every time you start up one of these long-running functions, you'll give it an "ID". + When the function completes, it will send to the GUI Event Loop a message with + the format: + work_id ::: done + This makes it easy to parse out your original work ID + + You can hard code these IDs to make your code more readable. For example, maybe + you have a function named "update_user_list()". You can call the work ID "user list". + Then check for the message coming back later from the work task to see if it starts + with "user list". If so, then that long-running task is over. - You want to look for 3 points in this code. - 1. Where you put your call that takes a long time - 2. Where the trigger to make the call takes place in the event loop - 3. Where the completion of the call is indicated in the event loop """ -# Put your.... - - ###### ######## ## ## -## ## ## ## ## ## -## ## ## ## ## -## ######## ## ## -## ## ## ## -## ## ## ## ## - ###### ## ####### - -#### ## ## ######## ######## ## ## ###### #### ## ## ######## - ## ### ## ## ## ### ## ## ## ## ## ## ## - ## #### ## ## ## #### ## ## ## ## ## ## - ## ## ## ## ## ###### ## ## ## ###### ## ## ## ###### - ## ## #### ## ## ## #### ## ## ## ## ## - ## ## ### ## ## ## ### ## ## ## ## ## ## -#### ## ## ## ######## ## ## ###### #### ### ######## - - ###### ####### ######## ######## -## ## ## ## ## ## ## -## ## ## ## ## ## -## ## ## ## ## ###### -## ## ## ## ## ## -## ## ## ## ## ## ## - ###### ####### ######## ######## - -# Here in this thread - -def worker_thread(thread_name, gui_queue): - print('Starting thread - {} '.format(thread_name)) +# ############################# User callable CPU intensive code ############################# +# Put your long running code inside this "wrapper" +# NEVER make calls to PySimpleGUI from this thread (or any thread)! +# Create one of these functions for EVERY long-running call you want to make +def long_function_wrapper(work_id, gui_queue): + print('Thread starting - {} '.format(work_id)) # LOCATION 1 # this is our "long running function call" time.sleep(5) # sleep for a while - print('Ending thread - {} '.format(thread_name)) - + print('Thread Ending - {} '.format(work_id)) # at the end of the work, before exiting, send a message back to the GUI indicating end - # in this case, we're using a simple string - gui_queue.put('{} - done'.format(thread_name)) # put a message into queue for GUI + gui_queue.put('{} ::: done'.format(work_id)) -######## ## ## #### -## ## ## ## ## -## ## ## ## -## #### ## ## ## -## ## ## ## ## -## ## ## ## ## -######## ######### #### - +############################# Begin GUI code ############################# def the_gui(): - gui_queue = queue.Queue() # queue used to communicate between the gui and the threads + gui_queue = queue.Queue() # queue used to communicate between the gui and long-running code layout = [[sg.Text('Multithreaded Work Example')], + [sg.Text('Click Go to start a long-running function call')], [sg.Text('', size=(25, 1), key='_OUTPUT_')], - # [sg.Output(size=(40,6))], + [sg.Text('', size=(25, 1), key='_OUTPUT2_')], [sg.Button('Go'), sg.Button('Popup'), sg.Button('Exit')], ] window = sg.Window('Multithreaded Window').Layout(layout) # --------------------- EVENT LOOP --------------------- - count = 0 + work_id = 0 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 if event == 'Go': # clicking "Go" starts a long running work item by starting thread - window.Element('_OUTPUT_').Update('Starting long work %s'%count) + window.Element('_OUTPUT_').Update('Starting long work %s'%work_id) # LOCATION 2 # STARTING long run by starting a thread - threading.Thread(target=worker_thread, args=('Thread %s'%count, gui_queue,), daemon=True).start() - count += 1 + threading.Thread(target=long_function_wrapper, args=(work_id, gui_queue,), daemon=True).start() + work_id += 1 # --------------- Read next message coming in from threads --------------- try: - message = gui_queue.get_nowait() # see if something has been posted to Queue - except queue.Empty: # get_nowait() will get exception when Queue is empty - message = None # nothing in queue so do nothing + message = gui_queue.get_nowait() # see if something has been posted to Queue + except queue.Empty: # get_nowait() will get exception when Queue is empty + message = None # nothing in queue so do nothing - # if message received from queue, display the message in the Window + # if message received from queue, then some work was completed if message is not None: # LOCATION 3 # this is the place you would execute code at ENDING of long running task - window.Element('_OUTPUT_').Update(message) - + # You can check the completed_work_id variable to see exactly which long-running function completed + completed_work_id = message[:message.index(' :::')] + window.Element('_OUTPUT2_').Update('Complete Work ID "{}"'.format(completed_work_id)) if event == 'Popup': sg.Popup('This is a popup showing that the GUI is running') # if user exits the window, then close the window and exit the GUI func window.Close() - -## ## ### #### ## ## -### ### ## ## ## ### ## -#### #### ## ## ## #### ## -## ### ## ## ## ## ## ## ## -## ## ######### ## ## #### -## ## ## ## ## ## ### -## ## ## ## #### ## ## +############################# Main ############################# if __name__ == '__main__': the_gui()