Changed multi-threaded demos to use new Window.write_event_value method
This commit is contained in:
parent
934cea5881
commit
04dfa364d5
|
@ -22,17 +22,20 @@ def process_thread():
|
||||||
proc = subprocess.run('pip list', shell=True, stdout=subprocess.PIPE)
|
proc = subprocess.run('pip list', shell=True, stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
|
||||||
thread = threading.Thread(target=process_thread, daemon=True)
|
def main():
|
||||||
thread.start()
|
thread = threading.Thread(target=process_thread, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, 'Loading list of packages', time_between_frames=100)
|
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, 'Loading list of packages', time_between_frames=100)
|
||||||
thread.join(timeout=.1)
|
thread.join(timeout=.1)
|
||||||
if not thread.is_alive():
|
if not thread.is_alive():
|
||||||
break
|
break
|
||||||
sg.popup_animated(None)
|
sg.popup_animated(None)
|
||||||
|
|
||||||
output = proc.__str__().replace('\\r\\n', '\n')
|
output = proc.__str__().replace('\\r\\n', '\n')
|
||||||
sg.popup_scrolled(output, font='Courier 10')
|
sg.popup_scrolled(output, font='Courier 10')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
#!/usr/bin/python3
|
#!/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 threading
|
||||||
import time
|
import time
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -16,14 +10,13 @@ import PySimpleGUI as sg
|
||||||
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
|
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
|
||||||
Other parts of the software are implemented as threads
|
Other parts of the software are implemented as threads
|
||||||
|
|
||||||
A queue.Queue is used by the worker threads to communicate with code that calls PySimpleGUI directly.
|
While users never know the implementation details within PySimpleGUI, the mechanism is that a queue.Queue
|
||||||
|
is used to communicate data between a thread and a PySimpleGUI window.
|
||||||
The PySimpleGUI code is structured just like a typical PySimpleGUI program. A layout defined,
|
The PySimpleGUI code is structured just like a typical PySimpleGUI program. A layout defined,
|
||||||
a Window is created, and an event loop is executed.
|
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
|
Copyright 2020 PySimpleGUI.org
|
||||||
You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,55 +28,58 @@ import PySimpleGUI as sg
|
||||||
# ## ## ## ## ## ## ## ## ## ##
|
# ## ## ## ## ## ## ## ## ## ##
|
||||||
# ## ## ## ## ## ######## ## ## ########
|
# ## ## ## ## ## ######## ## ## ########
|
||||||
|
|
||||||
def worker_thread1(thread_name, run_freq, gui_queue):
|
def worker_thread1(thread_name, run_freq, window):
|
||||||
"""
|
"""
|
||||||
A worker thread that communicates with the GUI
|
A worker thread that communicates with the GUI
|
||||||
These threads can call functions that block without affecting the GUI (a good thing)
|
These threads can call functions that block without affecting the GUI (a good thing)
|
||||||
Note that this function is the code started as each thread. All threads are identical in this way
|
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 thread_name: Text name used for displaying info
|
||||||
:param run_freq: How often the thread should run in milliseconds
|
:param run_freq: How often the thread should run in milliseconds
|
||||||
:param gui_queue: Queue used to communicate with the GUI
|
:param window: window this thread will be conversing with
|
||||||
|
:type window: sg.Window
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print('Starting thread 1 - {} that runs every {} ms'.format(thread_name, run_freq))
|
print('Starting thread 1 - {} that runs every {} ms'.format(thread_name, run_freq))
|
||||||
for i in itertools.count(): # loop forever, keeping count in i as it loops
|
for i in itertools.count(): # loop forever, keeping count in i as it loops
|
||||||
time.sleep(run_freq/1000) # sleep for a while
|
time.sleep(run_freq/1000) # sleep for a while
|
||||||
# put a message into queue for GUI
|
# put a message into queue for GUI
|
||||||
gui_queue.put('{} - {}'.format(thread_name, i))
|
window.write_event_value(thread_name, f'count = {i}')
|
||||||
|
|
||||||
|
|
||||||
def worker_thread2(thread_name, run_freq, gui_queue):
|
def worker_thread2(thread_name, run_freq, window):
|
||||||
"""
|
"""
|
||||||
A worker thread that communicates with the GUI
|
A worker thread that communicates with the GUI
|
||||||
These threads can call functions that block without affecting the GUI (a good thing)
|
These threads can call functions that block without affecting the GUI (a good thing)
|
||||||
Note that this function is the code started as each thread. All threads are identical in this way
|
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 thread_name: Text name used for displaying info
|
||||||
:param run_freq: How often the thread should run in milliseconds
|
:param run_freq: How often the thread should run in milliseconds
|
||||||
:param gui_queue: Queue used to communicate with the GUI
|
:param window: window this thread will be conversing with
|
||||||
|
:type window: sg.Window
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print('Starting thread 2 - {} that runs every {} ms'.format(thread_name, run_freq))
|
print('Starting thread 2 - {} that runs every {} ms'.format(thread_name, run_freq))
|
||||||
for i in itertools.count(): # loop forever, keeping count in i as it loops
|
for i in itertools.count(): # loop forever, keeping count in i as it loops
|
||||||
time.sleep(run_freq/1000) # sleep for a while
|
time.sleep(run_freq/1000) # sleep for a while
|
||||||
# put a message into queue for GUI
|
# put a message into queue for GUI
|
||||||
gui_queue.put('{} - {}'.format(thread_name, i))
|
window.write_event_value(thread_name, f'count = {i}')
|
||||||
|
|
||||||
|
|
||||||
def worker_thread3(thread_name, run_freq, gui_queue):
|
def worker_thread3(thread_name, run_freq, window):
|
||||||
"""
|
"""
|
||||||
A worker thread that communicates with the GUI
|
A worker thread that communicates with the GUI
|
||||||
These threads can call functions that block without affecting the GUI (a good thing)
|
These threads can call functions that block without affecting the GUI (a good thing)
|
||||||
Note that this function is the code started as each thread. All threads are identical in this way
|
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 thread_name: Text name used for displaying info
|
||||||
:param run_freq: How often the thread should run in milliseconds
|
:param run_freq: How often the thread should run in milliseconds
|
||||||
:param gui_queue: Queue used to communicate with the GUI
|
:param window: window this thread will be conversing with
|
||||||
|
:type window: sg.Window
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
print('Starting thread 3 - {} that runs every {} ms'.format(thread_name, run_freq))
|
print('Starting thread 3 - {} that runs every {} ms'.format(thread_name, run_freq))
|
||||||
for i in itertools.count(): # loop forever, keeping count in i as it loops
|
for i in itertools.count(): # loop forever, keeping count in i as it loops
|
||||||
time.sleep(run_freq/1000) # sleep for a while
|
time.sleep(run_freq/1000) # sleep for a while
|
||||||
# put a message into queue for GUI
|
# put a message into queue for GUI
|
||||||
gui_queue.put('{} - {}'.format(thread_name, i))
|
window.write_event_value(thread_name, f'count = {i}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,7 +92,7 @@ def worker_thread3(thread_name, run_freq, gui_queue):
|
||||||
# ###### ####### ####
|
# ###### ####### ####
|
||||||
|
|
||||||
|
|
||||||
def the_gui(gui_queue):
|
def the_gui():
|
||||||
"""
|
"""
|
||||||
Starts and executes the GUI
|
Starts and executes the GUI
|
||||||
Reads data from a Queue and displays the data to the window
|
Reads data from a Queue and displays the data to the window
|
||||||
|
@ -107,28 +103,30 @@ def the_gui(gui_queue):
|
||||||
"""
|
"""
|
||||||
layout = [[sg.Text('Multithreaded Window Example')],
|
layout = [[sg.Text('Multithreaded Window Example')],
|
||||||
[sg.Text('', size=(15, 1), key='-OUTPUT-')],
|
[sg.Text('', size=(15, 1), key='-OUTPUT-')],
|
||||||
[sg.Output(size=(40, 6))],
|
[sg.Multiline(size=(40, 26), key='-ML-', autoscroll=True)],
|
||||||
[sg.Button('Exit')], ]
|
[sg.Button('Exit')], ]
|
||||||
|
|
||||||
window = sg.Window('Multithreaded Window', layout)
|
window = sg.Window('Multithreaded Window', layout, finalize=True)
|
||||||
|
|
||||||
|
# -- Create a Queue to communicate with GUI --
|
||||||
|
# queue used to communicate between the gui and the threads
|
||||||
|
# -- Start worker threads, each taking a different amount of time
|
||||||
|
threading.Thread(target=worker_thread1, args=('Thread 1', 500, window,), daemon=True).start()
|
||||||
|
threading.Thread(target=worker_thread2, args=('Thread 2', 200, window,), daemon=True).start()
|
||||||
|
threading.Thread(target=worker_thread3, args=('Thread 3', 1000, window,), daemon=True).start()
|
||||||
|
# -- Start the GUI passing in the Queue --
|
||||||
|
|
||||||
|
sg.cprint_set_output_destination(window, '-ML-')
|
||||||
|
|
||||||
|
colors = {'Thread 1':('white', 'red'), 'Thread 2':('white', 'purple'), 'Thread 3':('white', 'blue')}
|
||||||
# --------------------- EVENT LOOP ---------------------
|
# --------------------- EVENT LOOP ---------------------
|
||||||
while True:
|
while True:
|
||||||
# wait for up to 100 ms for a GUI event
|
# wait for up to 100 ms for a GUI event
|
||||||
event, values = window.read(timeout=100)
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
# --------------- Loop through all messages coming in from threads ---------------
|
# --------------- Loop through all messages coming in from threads ---------------
|
||||||
while True: # loop executes until runs out of messages in Queue
|
sg.cprint(event, values[event], c=colors[event])
|
||||||
try: # see if something has been posted to Queue
|
|
||||||
message = gui_queue.get_nowait()
|
|
||||||
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['-OUTPUT-'].update(message)
|
|
||||||
# do a refresh because could be showing multiple messages before next Read
|
|
||||||
window.refresh()
|
|
||||||
print(message)
|
|
||||||
# if user exits the window, then close the window and exit the GUI func
|
# if user exits the window, then close the window and exit the GUI func
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
@ -142,16 +140,5 @@ def the_gui(gui_queue):
|
||||||
## ## ## ## #### ## ##
|
## ## ## ## #### ## ##
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# -- Create a Queue to communicate with GUI --
|
the_gui()
|
||||||
# queue used to communicate between the gui and the threads
|
|
||||||
gui_queue = queue.Queue()
|
|
||||||
# -- Start worker threads, each taking a different amount of time
|
|
||||||
threading.Thread(target=worker_thread1, args=(
|
|
||||||
'Thread 1', 500, gui_queue,), daemon=True).start()
|
|
||||||
threading.Thread(target=worker_thread2, args=(
|
|
||||||
'Thread 2', 200, gui_queue,), daemon=True).start()
|
|
||||||
threading.Thread(target=worker_thread3, args=(
|
|
||||||
'Thread 3', 1000, gui_queue,), daemon=True).start()
|
|
||||||
# -- Start the GUI passing in the Queue --
|
|
||||||
the_gui(gui_queue)
|
|
||||||
print('Exiting Program')
|
|
||||||
|
|
|
@ -17,26 +17,21 @@ import PySimpleGUI as sg
|
||||||
long as the task is running.
|
long as the task is running.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
total = 100 # number of units that are used with the progress bar
|
|
||||||
message = '' # used by thread to send back a message to the main thread
|
|
||||||
progress = 0 # current progress up to a maximum of "total"
|
|
||||||
|
|
||||||
|
def long_operation_thread(seconds, window):
|
||||||
def long_operation_thread(seconds):
|
|
||||||
"""
|
"""
|
||||||
A worker thread that communicates with the GUI through a global message variable
|
A worker thread that communicates with the GUI through a global message variable
|
||||||
This thread can block for as long as it wants and the GUI will not be affected
|
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 seconds: (int) How long to sleep, the ultimate blocking call
|
||||||
"""
|
"""
|
||||||
|
progress = 0
|
||||||
global message, progress
|
|
||||||
|
|
||||||
print('Thread started - will sleep for {} seconds'.format(seconds))
|
print('Thread started - will sleep for {} seconds'.format(seconds))
|
||||||
for i in range(int(seconds * 10)):
|
for i in range(int(seconds * 10)):
|
||||||
time.sleep(.1) # sleep for a while
|
time.sleep(.1) # sleep for a while
|
||||||
progress += total / (seconds * 10)
|
progress += 100 / (seconds * 10)
|
||||||
|
window.write_event_value('-PROGRESS-', progress)
|
||||||
|
|
||||||
message = f'*** The thread says.... "I am finished" ***'
|
window.write_event_value('-THREAD-', '*** The thread says.... "I am finished" ***')
|
||||||
|
|
||||||
def the_gui():
|
def the_gui():
|
||||||
"""
|
"""
|
||||||
|
@ -44,47 +39,47 @@ def the_gui():
|
||||||
Reads data from a global variable and displays
|
Reads data from a global variable and displays
|
||||||
Returns when the user exits / closes the window
|
Returns when the user exits / closes the window
|
||||||
"""
|
"""
|
||||||
global message, progress
|
|
||||||
|
|
||||||
sg.theme('Light Brown 3')
|
sg.theme('Light Brown 3')
|
||||||
|
|
||||||
layout = [[sg.Text('Long task to perform example')],
|
layout = [[sg.Text('Long task to perform example')],
|
||||||
[sg.Output(size=(80, 12))],
|
[sg.MLine(size=(80, 12), k='-ML-', reroute_stdout=True,write_only=True, autoscroll=True, auto_refresh=True)],
|
||||||
[sg.Text('Number of seconds your task will take'),
|
[sg.Text('Number of seconds your task will take'),
|
||||||
sg.Input(key='-SECONDS-', size=(5, 1)),
|
sg.Input(key='-SECONDS-', focus=True, size=(5, 1)),
|
||||||
sg.Button('Do Long Task', bind_return_key=True),
|
sg.Button('Do Long Task', bind_return_key=True),
|
||||||
sg.CBox('ONE chunk, cannot break apart', key='-ONE CHUNK-')],
|
sg.CBox('ONE chunk, cannot break apart', key='-ONE CHUNK-')],
|
||||||
[sg.Text('Work progress'), sg.ProgressBar(total, size=(20, 20), orientation='h', key='-PROG-')],
|
[sg.Text('Work progress'), sg.ProgressBar(100, size=(20, 20), orientation='h', key='-PROG-')],
|
||||||
[sg.Button('Click Me'), sg.Button('Exit')], ]
|
[sg.Button('Click Me'), sg.Button('Exit')], ]
|
||||||
|
|
||||||
window = sg.Window('Multithreaded Demonstration Window', layout)
|
window = sg.Window('Multithreaded Demonstration Window', layout, finalize=True)
|
||||||
|
|
||||||
thread = None
|
|
||||||
|
|
||||||
|
timeout = thread = None
|
||||||
# --------------------- EVENT LOOP ---------------------
|
# --------------------- EVENT LOOP ---------------------
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read(timeout=100)
|
event, values = window.read(timeout=timeout)
|
||||||
|
# print(event, values)
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
elif event.startswith('Do') and not thread:
|
elif event.startswith('Do') and not thread:
|
||||||
print('Thread Starting! Long work....sending value of {} seconds'.format(float(values['-SECONDS-'])))
|
print('Thread Starting! Long work....sending value of {} seconds'.format(float(values['-SECONDS-'])))
|
||||||
thread = threading.Thread(target=long_operation_thread, args=(float(values['-SECONDS-']),), daemon=True)
|
timeout = 100 if values['-ONE CHUNK-'] else None
|
||||||
|
thread = threading.Thread(target=long_operation_thread, args=(float(values['-SECONDS-']),window), daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
if values['-ONE CHUNK-']:
|
||||||
|
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
|
||||||
elif event == 'Click Me':
|
elif event == 'Click Me':
|
||||||
print('Your GUI is alive and well')
|
print('Your GUI is alive and well')
|
||||||
|
elif event == '-PROGRESS-':
|
||||||
if thread: # If thread is running
|
if not values['-ONE CHUNK-']:
|
||||||
if values['-ONE CHUNK-']: # If one big operation, show an animated GIF
|
window['-PROG-'].update_bar(values[event], 100)
|
||||||
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
|
elif event == '-THREAD-': # Thread has completed
|
||||||
else: # Not one big operation, so update a progress bar instead
|
|
||||||
window['-PROG-'].update_bar(progress, total)
|
|
||||||
thread.join(timeout=0)
|
thread.join(timeout=0)
|
||||||
if not thread.is_alive(): # the thread finished
|
print('Thread finished')
|
||||||
print(f'message = {message}')
|
sg.popup_animated(None) # stop animination in case one is running
|
||||||
sg.popup_animated(None) # stop animination in case one is running
|
thread, message, progress, timeout = None, '', 0, None # reset variables for next run
|
||||||
thread, message, progress = None, '', 0 # reset variables for next run
|
window['-PROG-'].update_bar(0,0) # clear the progress bar
|
||||||
window['-PROG-'].update_bar(0,0) # clear the progress bar
|
if values['-ONE CHUNK-'] and thread is not None:
|
||||||
|
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
import queue
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
DESIGN PATTERN - Multithreaded Long Tasks GUI
|
DESIGN PATTERN - Multithreaded Long Tasks GUI
|
||||||
|
@ -13,18 +10,20 @@ import PySimpleGUI as sg
|
||||||
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
|
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.
|
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
|
July 2020 - Note that this program has been updated to use the new Window.write_event_value method.
|
||||||
|
This method has not yet been ported to the other PySimpleGUI ports and is thus limited to the tkinter ports for now.
|
||||||
|
|
||||||
|
Internally to PySimpleGUI, 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,
|
The PySimpleGUI code is structured just like a typical PySimpleGUI program. A layout defined,
|
||||||
a Window is created, and an event loop is executed.
|
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
|
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
|
You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def long_operation_thread(seconds, gui_queue):
|
def long_operation_thread(seconds, window):
|
||||||
"""
|
"""
|
||||||
A worker thread that communicates with the GUI through a 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
|
This thread can block for as long as it wants and the GUI will not be affected
|
||||||
|
@ -34,7 +33,7 @@ def long_operation_thread(seconds, gui_queue):
|
||||||
"""
|
"""
|
||||||
print('Starting thread - will sleep for {} seconds'.format(seconds))
|
print('Starting thread - will sleep for {} seconds'.format(seconds))
|
||||||
time.sleep(seconds) # sleep for a while
|
time.sleep(seconds) # sleep for a while
|
||||||
gui_queue.put('** Done **') # put a message into queue for GUI
|
window.write_event_value('-THREAD-', '** DONE **') # put a message into queue for GUI
|
||||||
|
|
||||||
|
|
||||||
def the_gui():
|
def the_gui():
|
||||||
|
@ -44,7 +43,6 @@ def the_gui():
|
||||||
Returns when the user exits / closes the window
|
Returns when the user exits / closes the window
|
||||||
"""
|
"""
|
||||||
sg.theme('Light Brown 3')
|
sg.theme('Light Brown 3')
|
||||||
gui_queue = queue.Queue() # queue used to communicate between the gui and the threads
|
|
||||||
|
|
||||||
layout = [[sg.Text('Long task to perform example')],
|
layout = [[sg.Text('Long task to perform example')],
|
||||||
[sg.Output(size=(70, 12))],
|
[sg.Output(size=(70, 12))],
|
||||||
|
@ -57,29 +55,17 @@ def the_gui():
|
||||||
|
|
||||||
# --------------------- EVENT LOOP ---------------------
|
# --------------------- EVENT LOOP ---------------------
|
||||||
while True:
|
while True:
|
||||||
event, values = window.read(timeout=100)
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
elif event.startswith('Do'):
|
elif event.startswith('Do'):
|
||||||
try:
|
seconds = int(values['-SECONDS-'])
|
||||||
seconds = int(values['-SECONDS-'])
|
print('Thread ALIVE! Long work....sending value of {} seconds'.format(seconds))
|
||||||
print('Thread ALIVE! Long work....sending value of {} seconds'.format(seconds))
|
threading.Thread(target=long_operation_thread, args=(seconds, window,), daemon=True).start()
|
||||||
threading.Thread(target=long_operation_thread,
|
|
||||||
args=(seconds, gui_queue,), daemon=True).start()
|
|
||||||
except Exception as e:
|
|
||||||
print('Error starting work thread. Bad seconds input: "%s"' %
|
|
||||||
values['-SECONDS-'])
|
|
||||||
elif event == 'Click Me':
|
elif event == 'Click Me':
|
||||||
print('Your GUI is alive and well')
|
print('Your GUI is alive and well')
|
||||||
# --------------- Check for incoming messages from threads ---------------
|
elif event == '-THREAD-':
|
||||||
try:
|
print('Got a message back from the thread: ', values[event])
|
||||||
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
|
# if user exits the window, then close the window and exit the GUI func
|
||||||
window.close()
|
window.close()
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
import queue
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
@ -41,13 +40,13 @@ import PySimpleGUI as sg
|
||||||
# Create one of these functions for EVERY long-running call you want to make
|
# Create one of these functions for EVERY long-running call you want to make
|
||||||
|
|
||||||
|
|
||||||
def long_function_wrapper(work_id, gui_queue):
|
def long_function_wrapper(work_id, window):
|
||||||
# LOCATION 1
|
# LOCATION 1
|
||||||
# this is our "long running function call"
|
# this is our "long running function call"
|
||||||
# sleep for a while as a simulation of a long-running computation
|
# sleep for a while as a simulation of a long-running computation
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
# at the end of the work, before exiting, send a message back to the GUI indicating end
|
# at the end of the work, before exiting, send a message back to the GUI indicating end
|
||||||
gui_queue.put('{} ::: done'.format(work_id))
|
window.write_event_value('-THREAD DONE-', work_id)
|
||||||
# at this point, the thread exits
|
# at this point, the thread exits
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -56,8 +55,6 @@ def long_function_wrapper(work_id, gui_queue):
|
||||||
def the_gui():
|
def the_gui():
|
||||||
sg.theme('Light Brown 3')
|
sg.theme('Light Brown 3')
|
||||||
|
|
||||||
# queue used to communicate between the gui and long-running code
|
|
||||||
gui_queue = queue.Queue()
|
|
||||||
|
|
||||||
layout = [[sg.Text('Multithreaded Work Example')],
|
layout = [[sg.Text('Multithreaded Work Example')],
|
||||||
[sg.Text('Click Go to start a long-running function call')],
|
[sg.Text('Click Go to start a long-running function call')],
|
||||||
|
@ -71,7 +68,7 @@ def the_gui():
|
||||||
work_id = 0
|
work_id = 0
|
||||||
while True:
|
while True:
|
||||||
# wait for up to 100 ms for a GUI event
|
# wait for up to 100 ms for a GUI event
|
||||||
event, values = window.read(timeout=100)
|
event, values = window.read()
|
||||||
if event in (sg.WIN_CLOSED, 'Exit'):
|
if event in (sg.WIN_CLOSED, 'Exit'):
|
||||||
break
|
break
|
||||||
if event == 'Go': # clicking "Go" starts a long running work item by starting thread
|
if event == 'Go': # clicking "Go" starts a long running work item by starting thread
|
||||||
|
@ -81,23 +78,18 @@ def the_gui():
|
||||||
# STARTING long run by starting a thread
|
# STARTING long run by starting a thread
|
||||||
thread_id = threading.Thread(
|
thread_id = threading.Thread(
|
||||||
target=long_function_wrapper,
|
target=long_function_wrapper,
|
||||||
args=(work_id, gui_queue,),
|
args=(work_id, window,),
|
||||||
daemon=True)
|
daemon=True)
|
||||||
thread_id.start()
|
thread_id.start()
|
||||||
work_id = work_id+1 if work_id < 19 else 0
|
work_id = work_id+1 if work_id < 19 else 0
|
||||||
# --------------- 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
|
|
||||||
|
|
||||||
# if message received from queue, then some work was completed
|
# if message received from queue, then some work was completed
|
||||||
if message is not None:
|
if event == '-THREAD DONE-':
|
||||||
# LOCATION 3
|
# LOCATION 3
|
||||||
# this is the place you would execute code at ENDING of long running task
|
# this is the place you would execute code at ENDING of long running task
|
||||||
# You can check the completed_work_id variable
|
# You can check the completed_work_id variable
|
||||||
# to see exactly which long-running function completed
|
# to see exactly which long-running function completed
|
||||||
completed_work_id = int(message[:message.index(' :::')])
|
completed_work_id = values[event]
|
||||||
window['-OUTPUT2-'].update(
|
window['-OUTPUT2-'].update(
|
||||||
'Complete Work ID "{}"'.format(completed_work_id))
|
'Complete Work ID "{}"'.format(completed_work_id))
|
||||||
window[completed_work_id].update(text_color='green')
|
window[completed_work_id].update(text_color='green')
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import PySimpleGUI as sg
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Threaded Demo - Uses Window.write_event_value communications
|
Threaded Demo - Uses Window.write_event_value communications
|
||||||
|
|
||||||
Requires PySimpleGUI.py version 4.24.0.17
|
Requires PySimpleGUI.py version 4.25.0 and later
|
||||||
|
|
||||||
Demo of threads using a new way of communicating with threads that is done in a non-polled way.
|
This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI.
|
||||||
No longer do you need to run your event loop with a timeout value in order to multi-thread.
|
|
||||||
Now you can pend on your read forever and use a special call that threads can call that will add a new item to the queue
|
|
||||||
of items
|
|
||||||
|
|
||||||
|
Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications
|
||||||
|
queue is now performed internally to PySimpleGUI.
|
||||||
|
|
||||||
|
The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in
|
||||||
|
a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling.
|
||||||
|
|
||||||
|
Copyright 2020 PySimpleGUI.org
|
||||||
"""
|
"""
|
||||||
|
|
||||||
THREAD_EVENT = '-THEAD-'
|
THREAD_EVENT = '-THREAD-'
|
||||||
|
|
||||||
def the_thread(window):
|
def the_thread(window):
|
||||||
"""
|
"""
|
||||||
|
@ -25,7 +30,8 @@ def the_thread(window):
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
window.write_event_value(THREAD_EVENT, i)
|
sg.cprint(f'thread info (in thread) = {threading.current_thread().name}', c='white on purple')
|
||||||
|
window.write_event_value('-THREAD-', i)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,6 +53,7 @@ def main():
|
||||||
while True: # Event Loop
|
while True: # Event Loop
|
||||||
event, values = window.read()
|
event, values = window.read()
|
||||||
sg.cprint(event, values)
|
sg.cprint(event, values)
|
||||||
|
sg.cprint(f'thread info = {threading.current_thread().name}')
|
||||||
if event == sg.WIN_CLOSED or event == 'Exit':
|
if event == sg.WIN_CLOSED or event == 'Exit':
|
||||||
break
|
break
|
||||||
if event == 'Start':
|
if event == 'Start':
|
||||||
|
|
Loading…
Reference in New Issue