Python, Tkinter - ttk.Progressbar in a separate thread - python

I googled a lot on this but I still haven't found what I'm looking for.
This is a classic question, I guess, but I still can't figure it out.
I have this Python/Tkinter code. The code starts a pretty CPU-heavy process by trivially calling it with os.system(cmd). I want a progress bar (oscillating one, not progressive) which shows users something is actually happening.
I guess I just have to kind of start the thread containing the progress bar before calling os.system, then call os.system while progress bar thread is running, close progress bar thread and destroy associate Toplevel().
I mean, Python is pretty flexible, is it possible to do this without much pain?
I know killing a thread from another thread is unsafe (due to data sharing), but this two threads do not share any data as far as I know.
Would it be possible to go like this:
progressbar_thread.start()
os.system(...)
progressbar_thread.kill()
If that's not possible, I still don't understand how to pass 'signal' variables between the two threads.
Thank you,
Andrea

Is this the type of thing you are after?
from Tkinter import *
import ttk, threading
class progress():
def __init__(self, parent):
toplevel = Toplevel(tk)
self.progressbar = ttk.Progressbar(toplevel, orient = HORIZONTAL, mode = 'indeterminate')
self.progressbar.pack()
self.t = threading.Thread()
self.t.__init__(target = self.progressbar.start, args = ())
self.t.start()
#if self.t.isAlive() == True:
# print 'worked'
def end(self):
if self.t.isAlive() == False:
self.progressbar.stop()
self.t.join()
def printmsg():
print 'proof a new thread is running'
tk = Tk()
new = progress(tk)
but1 = ttk.Button(tk, text= 'stop', command= new.end)
but2 = ttk.Button(tk, text = 'test', command= printmsg)
but1.pack()
but2.pack()
tk.mainloop()

You don't need threads in this case. Just use subprocess.Popen to start the subprocess.
To notify the GUI when the process ends you could implement polling using widget.after() method:
process = Popen(['/path/to/command', 'arg1', 'arg2', 'etc'])
progressbar.start()
def poller():
if process.poll() is None: # process is still running
progressbar.after(delay, poller) # continue polling
else:
progressbar.stop() # process ended; stop progress bar
delay = 100 # milliseconds
progressbar.after(delay, poller) # call poller() in `delay` milliseconds
If you want to stop the process manually without waiting:
if process.poll() is None: # process is still running
process.terminate()
# kill process in a couple of seconds if it is not terminated
progressbar.after(2000, kill_process, process)
def kill_process(process):
if process.poll() is None:
process.kill()
process.wait()
Here's a complete example.

Related

tkinter, master window does not loop after I start a thread [duplicate]

My interface is freezing on pressing the button. I am using threading but I am not sure why is still hanging. Any help will be appreciated. Thanks in advance
class magic:
def __init__(self):
self.mainQueue=queue.Queue()
def addItem(self,q):
self.mainQueue.put(q)
def startConverting(self,funcName):
if(funcName=="test"):
while not self.mainQueue.empty():
t = Thread(target = self.threaded_function)
t.start()
t.join()
def threaded_function(self):
time.sleep(5)
print(self.mainQueue.get())
m=magic()
def helloCallBack():
m.addItem("asd")
m.startConverting("test") //this line of code is freezing
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Here's a recipe for doing an asynchronous task with a tkinter-based GUI. I adapted it from a recipe in the cited book. You should be able to modify it to do what you need.
To keep the GUI responsive requires not interfering with its mainloop() by doing something like join()ing a background thread—which makes the GUI "hang" until the thread is finished. This is accomplished by using the universal after() widget method to poll a Queue at regular intervals.
# from "Python Coobook 2nd Edition", section 11.9, page 439.
# Modified to work in Python 2 & 3.
from __future__ import print_function
try:
import Tkinter as tk, time, threading, random, Queue as queue
except ModuleNotFoundError: # Python 3
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, end_command):
self.queue = queue
# Set up the GUI
tk.Button(master, text='Done', command=end_command).pack()
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print(msg)
except queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.worker_thread1)
self.thread1.start()
# Start the periodic call in the GUI to check the queue
self.periodic_call()
def periodic_call(self):
""" Check every 200 ms if there is something new in the queue. """
self.master.after(200, self.periodic_call)
self.gui.processIncoming()
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random() * 1.5)
msg = rand.random()
self.queue.put(msg)
def end_application(self):
self.running = False # Stops worker_thread1 (invoked by "Done" button).
rand = random.Random()
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
For anyone having a problem with sys.exit(1) in #martineau's code - if you replace sys.exit(1) with self.master.destroy() the program ends gracefully. I lack the reputation to add a comment, hence the seperate answer.

Freezing/Hanging tkinter GUI in waiting for the thread to complete

My interface is freezing on pressing the button. I am using threading but I am not sure why is still hanging. Any help will be appreciated. Thanks in advance
class magic:
def __init__(self):
self.mainQueue=queue.Queue()
def addItem(self,q):
self.mainQueue.put(q)
def startConverting(self,funcName):
if(funcName=="test"):
while not self.mainQueue.empty():
t = Thread(target = self.threaded_function)
t.start()
t.join()
def threaded_function(self):
time.sleep(5)
print(self.mainQueue.get())
m=magic()
def helloCallBack():
m.addItem("asd")
m.startConverting("test") //this line of code is freezing
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
top.mainloop()
Here's a recipe for doing an asynchronous task with a tkinter-based GUI. I adapted it from a recipe in the cited book. You should be able to modify it to do what you need.
To keep the GUI responsive requires not interfering with its mainloop() by doing something like join()ing a background thread—which makes the GUI "hang" until the thread is finished. This is accomplished by using the universal after() widget method to poll a Queue at regular intervals.
# from "Python Coobook 2nd Edition", section 11.9, page 439.
# Modified to work in Python 2 & 3.
from __future__ import print_function
try:
import Tkinter as tk, time, threading, random, Queue as queue
except ModuleNotFoundError: # Python 3
import tkinter as tk, time, threading, random, queue
class GuiPart(object):
def __init__(self, master, queue, end_command):
self.queue = queue
# Set up the GUI
tk.Button(master, text='Done', command=end_command).pack()
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
""" Handle all messages currently in the queue, if any. """
while self.queue.qsize():
try:
msg = self.queue.get_nowait()
# Check contents of message and do whatever is needed. As a
# simple example, let's print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print(msg)
except queue.Empty:
# just on general principles, although we don't expect this
# branch to be taken in this case, ignore this exception!
pass
class ThreadedClient(object):
"""
Launch the main part of the GUI and the worker thread. periodic_call()
and end_application() could reside in the GUI part, but putting them
here means that you have all the thread controls in a single place.
"""
def __init__(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = queue.Queue()
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.end_application)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = True
self.thread1 = threading.Thread(target=self.worker_thread1)
self.thread1.start()
# Start the periodic call in the GUI to check the queue
self.periodic_call()
def periodic_call(self):
""" Check every 200 ms if there is something new in the queue. """
self.master.after(200, self.periodic_call)
self.gui.processIncoming()
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
def worker_thread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select()'. One important thing to remember is that the thread has
to yield control pretty regularly, be it by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, create a random number at random
# intervals. Replace the following two lines with the real thing.
time.sleep(rand.random() * 1.5)
msg = rand.random()
self.queue.put(msg)
def end_application(self):
self.running = False # Stops worker_thread1 (invoked by "Done" button).
rand = random.Random()
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
For anyone having a problem with sys.exit(1) in #martineau's code - if you replace sys.exit(1) with self.master.destroy() the program ends gracefully. I lack the reputation to add a comment, hence the seperate answer.

Python 3 - KeyboardInterrupt in background thread not detected by main thread until user hovers over GUI window

I've written a Python 3 TkInter-based GUI application that launches a worker thread in the background. After the worker thread has finished, it waits two seconds (this is to avoid a possible race condition), and then sends a KeyboardInterrupt to tell the main thread it can close down.
Expected behaviour: running the program launches a GUI window, prints some text to the console after which the program closes down automatically.
Actual behaviour: instead of closing automatically, it only does so after the user either hovers the mouse over the GUI window area, or presses a key on the keyboard! Apart from that the program runs without reporting any errors.
Anyone have any idea why this is happening, and how to fix this? I already tried to wrap the KeyboardInterrupt into a separate function and then call that through a timer object, but this results in the same behaviour.
I've been able to reproduce this issue on 2 different Linux machines that run Python 3.5.2. and 3.6.6 respectively.
#! /usr/bin/env python3
import os
import threading
import _thread as thread
import time
import tkinter as tk
import tkinter.scrolledtext as ScrolledText
class myGUI(tk.Frame):
# This class defines the graphical user interface
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.root = parent
self.build_gui()
def build_gui(self):
# Build GUI
self.root.title('TEST')
self.root.option_add('*tearOff', 'FALSE')
self.grid(column=0, row=0, sticky='ew')
self.grid_columnconfigure(0, weight=1, uniform='a')
# Add text widget to display logging info
st = ScrolledText.ScrolledText(self, state='disabled')
st.configure(font='TkFixedFont')
st.grid(column=0, row=1, sticky='w', columnspan=4)
def worker():
"""Skeleton worker function, runs in separate thread (see below)"""
# Print some text to console
print("Working!")
# Wait 2 seconds to avoid race condition
time.sleep(2)
# This triggers a KeyboardInterrupt in the main thread
thread.interrupt_main()
def main():
try:
root = tk.Tk()
myGUI(root)
t1 = threading.Thread(target=worker, args=[])
t1.start()
root.mainloop()
t1.join()
except KeyboardInterrupt:
# Close program if subthread issues KeyboardInterrupt
os._exit(0)
main()
(Github Gist link to the above script here)
root.mainloop() mainloop is blocking and pending (interceptable) signals in Python are only examined in between execution of bytecode instructions. t1.join() in your code actually never gets executed.
Since mainloop block-waits for forwarded hardware-interrupts, for unblocking you have to provide them by e.g. hovering over the window like you saw. Only then the interpreter detects the pending KeyboardInterrupt. That's just how signal processing in Python works.
Solving the general problem could mean finding ways to unblock blocking I/O-calls by externally injecting what's needed to unblock them, or just not using blocking calls in the first place.
For your concrete setup, you could kill the whole process with an unhandled SIGTERM, but of course, that would be very, very ugly to do and is also unnecessary here. If you just search for a way to timeout your window, you can timeout with the tkinter.Tk.after method (shown here and here), or you get rid of mainloop and run your loop yourself (here).
The latter could look like:
def main():
root = tk.Tk()
myGUI(root)
t1 = threading.Thread(target=worker, args=[])
t1.start()
while True:
try:
root.update_idletasks()
root.update()
time.sleep(0.1)
except KeyboardInterrupt:
print('got interrupt')
break
t1.join()

Python multiprocessing asynchronous callback

I'm writing a program with a GUI using TKinter, in which the user can click a button and a new process is started to perform work using multiprocess.Process. This is necessary so the GUI can still be used while the work is being done, which can take several seconds.
The GUI also has a text box where the status of the program is displayed when things happen. This is often straight forward, with each function calling an add_text() function which just prints text in the text box. However, when add_text() is called in the separate process, the text does not end up in the text box.
I've thought about using a Pipe or Queue, but that would require using some sort of loop to check if anything has been returned from the process and that would also cause the main (GUI) process to be unusable. Is there some way to call a function in one process that will do work in another?
Here's an simple example of what I'm trying to do
import time
import multiprocessing as mp
import tkinter as tk
textbox = tk.Text()
def add_text(text):
# Insert text into textbox
textbox.insert(tk.END, text)
def worker():
x = 0
while x < 10:
add_text('Sleeping for {0} seconds'.format(x)
x += 1
time.sleep(1)
proc = mp.Process(target=worker)
# Usually happens on a button click
proc.start()
# GUI should still be usable here
The asyncronous things actually require loop.
You could attach function to the TkInter's loop by using Tk.after() method.
import Tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.check_processes()
self.root.mainloop()
def check_processes(self):
if process_finished:
do_something()
else:
do_something_else()
self.after(1000, check_processes)
app=App()
I ended up using a multiprocessing.Pipe by using TKinter's after() method to perform the looping. It loops on an interval and checks the pipe to see if there's any messages from the thread, and if so it inserts them into the text box.
import tkinter
import multiprocessing
def do_something(child_conn):
while True:
child_conn.send('Status text\n')
class Window:
def __init__(self):
self.root = tkinter.Tk()
self.textbox = tkinter.Text()
self.parent_conn, child_conn = multiprocessing.Pipe()
self.process = multiprocessing.Process(target=do_something, args=(child_conn,))
def start(self):
self.get_status_updates()
self.process.start()
self.root.mainloop()
def get_status_updates()
status = self.check_pipe()
if status:
self.textbox.add_text(status)
self.root.after(500, self.get_status_updates) # loop every 500ms
def check_pipe():
if self.parent_conn.poll():
status = self.parent_conn.recv()
return status
return None

Creating multiple instances of wx.App - is it ok?

so I need to implement a following scenario:
- Several tasks are running simultaneously as processes.
- Each task should display a progress bar with a "Cancel" button, clicking on which should terminate it.
To achieve responsive GUI, I run the task for each process in a separate thread, and it seems that I need to create a separate wx.App for each process as well, otherwise the thread seems to be not running. This setup works fine, however:
a) I am not sure whether multiple wx.App's is a good idea or
b) If there is a better way of achieving my goal.
MWE below (note: in this sample code I could have used Update method of wx.ProgressDialog to identify whether the "Cancel" button has been pressed, but can't do so for my real application).
import wx, multiprocessing, time, psutil
from multiprocessing import Queue
from threading import Thread
from wx.lib.pubsub import pub as Publisher
#runs the task
def task_runner(q):
pid = multiprocessing.current_process().pid
q.put(pid)
while True:
print("Process Running")
time.sleep(1)
wx.CallAfter(Publisher.sendMessage, "update") #call to update the bar
class TestPanel():
def __init__(self,name):
self.q = Queue()
self.count=0
max = 80
# dialog to see progress and cancel the task
self.dlg = wx.GenericProgressDialog(name,
"An informative message",
maximum = max,
parent=None,
style = wx.PD_CAN_ABORT
| wx.PD_APP_MODAL
| wx.PD_ELAPSED_TIME
)
#set listener to dialog's "Cancel" button
for child in self.dlg.GetChildren():
if isinstance(child, wx.Button):
cancel_function = lambda evt, parent=self.dlg: self.onClose(evt, parent)
child.Bind(wx.EVT_BUTTON, cancel_function)
#subscribe to update the progress bar from the thread
Publisher.subscribe(self.updateProgress, "update")
# start thread which runs some task
p = Thread(target=task_runner, args=(self.q,))
p.start()
#updates the progress bar
def updateProgress(self):
print("updating progress")
self.count=self.count+10
self.dlg.Update(self.count)
#kills the process
def kill(self, proc_pid):
process = psutil.Process(proc_pid)
for proc in process.children(recursive=True):
proc.kill()
process.kill()
#closing the dialog event
def onClose(self, event, dialog):
""""""
print "Closing dialog!"
pid = self.q.get()
self.kill(pid)
dialog.Destroy()
# run process, each process creates its own wx.App
def runProcess(name):
app = wx.App(False)
TestPanel(name)
app.MainLoop()
# worker class to use for multiprocessing pool
class Worker():
def __call__(self, name):
return runProcess(name)
if __name__ == '__main__':
items=['Bar1', 'Bar2']
pool = multiprocessing.Pool(processes=2)
result = pool.map(Worker(), items) #create two processes
pool.close()
No, having more than one wx.App in a single process is not a good idea. Even creating a new one after the prior is finished can sometimes be problematic.
However since you are using multiprocess it is not quite the same. Unless I'm missing something, each OS process does have just one wx.App in your case, and since the parent process is not creating a wx.App then they are not trying to inherit that one (which would likely cause even more problems.)

Categories

Resources