diff --git a/DemoPrograms/Demo_Multithreaded_Queued.py b/DemoPrograms/Demo_Multithreaded_Queued.py index f141d8b7..0c834201 100644 --- a/DemoPrograms/Demo_Multithreaded_Queued.py +++ b/DemoPrograms/Demo_Multithreaded_Queued.py @@ -1,8 +1,20 @@ -from queue import Queue -from threading import Thread -from time import sleep +#!/usr/bin/python3 +# Rather than importing individual classes such as threading.Thread or queue.Queue, this +# program is doing a simple import and then indicating the package name when the functions +# are called. This seemed like a great way for the reader of the code to get an understanding +# as to exactly which package is being used. It's purely for educational and explicitness purposes +import queue +import threading +import time +import itertools + +# 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 GUI @@ -21,60 +33,87 @@ import PySimpleGUI as sg """ +######## ## ## ######## ######## ### ######## + ## ## ## ## ## ## ## ## ## ## + ## ## ## ## ## ## ## ## ## ## + ## ######### ######## ###### ## ## ## ## + ## ## ## ## ## ## ######### ## ## + ## ## ## ## ## ## ## ## ## ## + ## ## ## ## ## ######## ## ## ######## + def worker_thread(thread_name, run_freq, gui_queue): """ A worker thrread that communicates with the GUI These threads can call functions that block withouth affecting the GUI (a good thing) + Note that this function is the code started as each thread. All threads are identical in this way :param thread_name: Text name used for displaying info :param run_freq: How often the thread should run in milliseconds :param gui_queue: Queue used to communicate with the GUI :return: """ - print('Starting thread - ', thread_name) - i = 0 - while True: - sleep(run_freq/1000) - gui_queue.put('{} - {}'.format(thread_name, i)) - i += 1 + print('Starting thread - {} that runds every {} ms'.format(thread_name, run_freq)) + for i in itertools.count(): # loop forever, keeping count in i as it loops + time.sleep(run_freq/1000) # sleep for a while + gui_queue.put('{} - {}'.format(thread_name, i)) # put a message into queue for GUI + ###### ## ## #### +## ## ## ## ## +## ## ## ## +## #### ## ## ## +## ## ## ## ## +## ## ## ## ## + ###### ####### #### + def the_gui(gui_queue): """ - starts and executes the GUI. Returns when the user exits / closes the window - reads data from a Queue and displays the data + 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 + (that means it does NOT return until the user exits the window) :param gui_queue: Queue the GUI should read from :return: """ - layout = [ [sg.Text('Your GUI Window')], + layout = [ [sg.Text('Multithreaded Window Example')], [sg.Text('', size=(15,1), key='_OUTPUT_')], [sg.Button('Exit')],] - window = sg.Window('Window Title').Layout(layout) - - while True: # Event Loop - event, values = window.Read(timeout=100) # wait for up to 200 ms for a GUI event + 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 - #--------------- Handle stuff coming in from threads --------------- + #--------------- Loop through all messages coming in from threads --------------- while True: # loop executes until runs out of messages in Queue try: # see if something has been posted to Queue message = gui_queue.get_nowait() - except: # will get exception when Queue is empty + except queue.Empty: # get_nowait() will get exception when Queue is empty break # break from the loop if no more messages are queued up # if message received from queue, display the message in the Window if message: window.Element('_OUTPUT_').Update(message) - window.Refresh() + window.Refresh() # do a refresh because could be showing multiple messages before next Read - # if user exits the window, then close the window and exit + # if user exits the window, then close the window and exit the GUI func window.Close() +## ## ### #### ## ## +### ### ## ## ## ### ## +#### #### ## ## ## #### ## +## ### ## ## ## ## ## ## ## +## ## ######### ## ## #### +## ## ## ## ## ## ### +## ## ## ## #### ## ## + if __name__ == '__main__': #-- Create a Queue to communicate with GUI -- - gui_queue = Queue() # queue used to communicate between the gui and the worker + gui_queue = queue.Queue() # queue used to communicate between the gui and the threads #-- Start worker threads, one runs twice as often as the other - Thread(target=worker_thread, args=('Thread 1', 500, gui_queue,), daemon=True).start() - Thread(target=worker_thread, args=('Thread 2', 200, gui_queue,), daemon=True).start() + threading.Thread(target=worker_thread, args=('Thread 1', 500, gui_queue,), daemon=True).start() + threading.Thread(target=worker_thread, args=('Thread 2', 200, gui_queue,), daemon=True).start() + threading.Thread(target=worker_thread, args=('Thread 3', 1000, gui_queue,), daemon=True).start() #-- Start the GUI passing in the Queue -- the_gui(gui_queue) + print('Exiting Program') \ No newline at end of file