I am trying to display a count variable from a background task in the main task which is my tkinter GUI. Why? I want to display that the long time taking background task is performing and later use this count variable to visualize it with a progress bar.
My problem is, that even when using a Queue, I am not able to display the count variable. Maybe I've got problems in understanding python and its behaviour with objects and/or threads.
import threading
import time
import Queue
import Tkinter as Tk
import Tkconstants as TkConst
from ScrolledText import ScrolledText
from tkFont import Font
import loop_simulation as loop
def on_after_elapsed():
while True:
try:
v = dataQ.get(timeout=0.1)
except:
break
scrText.insert(TkConst.END, str(v)) # "value=%d\n" % v
scrText.see(TkConst.END)
scrText.update()
top.after(100, on_after_elapsed)
def thread_proc1():
x = -1
dataQ.put(x)
x = loop.loop_simulation().start_counting()
# th_proc = threading.Thread(target=x.start_counting())
# th_proc.start()
for i in range(5):
for j in range(20):
dataQ.put(x.get_i())
time.sleep(0.1)
# x += 1
time.sleep(0.5)
dataQ.put(x.get_i())
top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
f = Font(family='Courier New', size=12)
scrText = ScrolledText(master=top, height=20, width=120, font=f)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15, expand=True)
th = threading.Thread(target=thread_proc1)
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
th.join()
In thread_proc1() I want to get the value of the counter of background task. This is the background task:
import time
class loop_simulation:
def __init__(self):
self.j = 0
# self.start_counting()
def start_counting(self):
for i in range(0, 1000000):
self.j = i
time.sleep(0.5)
def get_i(self):
return str(self.j)
The reason the count variable isn't being displayed is due to the
x = loop.loop_simulation().start_counting()
statement in thread_proc1(). This creates a loop_simulation instance and calls its start_counting() method. However, other than already inserting a -1 into the dataQ, thread_proc1() doesn't do anything else until start_counting() returns, which won't be for a long time (500K seconds).
Meanwhile, the rest of your script is running and displaying only that initial -1 that was put in.
Also note that if start_counting() ever did return, its value of None is going to be assigned to x which later code attempts to use with: x.get_i().
Below is reworking of your code that fixes these issues and also follows the PEP 8 - Style Guide for Python Code more closely. To avoid the main problem of calling start_counting(), I changed your loop_simulation class into a subclass of threading.Thread and renamed it LoopSimulation, and create an instance of it in thread_proc1, so there are now two background threads in addition to the main one handling the tkinter-based GUI.
import loop_simulation as loop
from ScrolledText import ScrolledText
import threading
import Tkinter as Tk
import Tkconstants as TkConst
from tkFont import Font
import time
import Queue
def on_after_elapsed():
# removes all data currently in the queue and displays it in the text box
while True:
try:
v = dataQ.get_nowait()
scrText.insert(TkConst.END, str(v)+'\n')
scrText.see(TkConst.END)
except Queue.Empty:
top.after(100, on_after_elapsed)
break
def thread_proc1():
dataQ.put(-1)
ls = loop.LoopSimulation() # slow background task Thread
ls.start_counting()
while ls.is_alive(): # background task still running?
for i in range(5):
for j in range(20):
dataQ.put(ls.get_i())
time.sleep(0.1)
time.sleep(0.5)
dataQ.put('background task finished')
top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
font = Font(family='Courier New', size=12)
scrText = ScrolledText(top, height=20, width=120, font=font)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15,
expand=TkConst.YES)
th = threading.Thread(target=thread_proc1)
th.daemon = True # OK for main to exit even if thread is still running
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
loop_simulation.py module:
import threading
import time
class LoopSimulation(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True # OK for main to exit even if instance still running
self.lock = threading.Lock()
self.j = 0
start_counting = threading.Thread.start # an alias for starting thread
def run(self):
for i in range(1000000):
with self.lock:
self.j = i
time.sleep(0.5)
def get_i(self):
with self.lock:
return self.j
Related
I am trying to manipulate some data in a thread from the main function. The issue I am facing about modifying some of the variables which are part of the function which is running on a thread.
So I am trying to run a Tkinter based GUI loop in a thread to ensure it is always running. And want to modify some of the label corresponding to the status in the main function execution. I am facing an issue where it is unable to locate the label variables in the main loop as it is part of the function running on the thread.
Below is a simplified psuedo code for that approach. Please suggest if this a correct way to the above task or is there is any better and efficient way.
import threading
def thread_func():
i = 0
while True:
print('i from thread: ', i)
if __name__ == '__main__':
t = threading.Thread(target=thread_func)
t.start()
while True:
i += 1
Actual scaled down simplified code
import threading
import tkinter as tk
def gui():
window = tk.Tk()
label = tk.Label(text='On')
label.pack()
window.mainloop()
if __name__ == '__main__':
t = threading.Thread(target=gui)
t.start()
while True:
label['text'] = 'Active'
Error:
Traceback (most recent call last):
File "test.py", line 17, in <module>
label['text'] = 'Active'
NameError: name 'label' is not defined
Is there a better way to keep the tkinter gui always on and perform some task in the loop?
Using class and threading:
import tkinter
class Test(tkinter.Label):
x = False
def __init__(self):
super().__init__()
self.pack()
self['text'] = 'On'
def gui(self):
if not self.x:
self['text'] = 'Active'
self.mainloop()
else:
self.mainloop()
if __name__ == '__main__':
label = Test()
while isinstance(label, Test):
t = threading.Thread(target=label.gui())
t.start()
When you write code of label there then you will get error because when program start it starts from creating thread, that thread will only end when tkinter window is close and same for previous thread_fuc code. And you wrote the label code after tkinter window is closed.
The above issue will be solved by doing this :
import threading
def thread_func():
while True:
print('i from thread: ', i)
def tt():
global i
while True:
i += 1
if __name__ == '__main__':
i=0
t = threading.Thread(target=thread_func)
t.start()
yt = threading.Thread(target=tt)
yt.start()
Making i global and running 2 function parallely. We have to global because we can't use the variable of one function to another. And additionally we are running 2 function in 2 thread.
And for your tkinter file as #TheLizzard suggest, you can use .after insted of using thread in tkinter if you want to change content constantly/want to use loop.
Here's the basic example how you can implement it:
import random
import tkinter as tk
app = tk.Tk()
app.geometry("200x220")
label = tk.Label(app, text="0")
label.pack()
def change(b=0):
if b < 30:
a = random.randrange(1, 7, 1)
label.config(text=a)
app.after(100, change, b+1)
b1 = tk.Button(app, text="Get New Number", command=change)
b1.pack()
app.mainloop()
For more explanation about it you may visit here.
After quite a bit of reading here about multiple processes, pipes, etc., I haven't found an answer yet, but my apologies if it already exists.
I have a piece of peripheral hardware for which I'm trying to create a GUI. I'd like to have the GUI get updated constantly with data from the peripheral, while still maintaining interactivity for the user. For example, I have a gain parameter that I'm using to drive a bargraph, and while that is constantly being updated, I'd like the user to be able to click a button to cause some action. Here is some example code. Despite my certainty that I have some serious mistakes here, this actually almost works, but the 'quit' button remains unresponsive:
#!/usr/bin/env python`
# -*- coding: utf-8 -*-
# 2014-07-24 S. Petit
import matplotlib.pyplot as plt
from serial import Serial
import serial, socket, time, datetime, sys, struct
from datetime import datetime
import numpy as np
import shutil
import os
from random import randint
from Tkinter import *
from multiprocessing import *
dcbSerialPort = 'COM10'
def getGainLNA(pipeToParent):
try:
S_dcb = Serial(dcbSerialPort, 115200, timeout=.1)
print 'Opened DCB at', dcbSerialPort
except:
print '\r\n'
print '*************************************************'
print 'ERROR: Unable to open', dcbSerialPort, 'serial connection.'
print '*************************************************'
print '\r\n'
raw_input()
exit()
while True:
promptFound = False
PICreturn = ''
S_dcb.write('gain\r')
while not promptFound:
PICreturn += S_dcb.read(S_dcb.inWaiting())
if 'DCB>' in PICreturn:
promptFound = True
gainLNA = float(PICreturn[20:28].strip())
gainLNA_scaled = int(100*(gainLNA/33))
pipeToParent.send(gainLNA_scaled)
return()
if __name__ == '__main__':
gainUpdaterPipe, gainUpdaterPipeChild = Pipe()
lnaGainUpdater = Process(target=getGainLNA, args=(gainUpdaterPipeChild,))
lnaGainUpdater.start()
root=Tk()
root.title = 'AGC'
while True:
if gainUpdaterPipe.poll():
gainLNA = gainUpdaterPipe.recv()
print gainLNA
quitButton = Button(text='Quit', command=quit)
quitButton.grid(row=1, column=0)
areaAGC = Canvas(width=120, height=100, bg='blue')
objectAGC = areaAGC.create_polygon(20,20, gainLNA,20, gainLNA,50, 20,50, outline='green', fill='yellow')
areaAGC.grid(row=0, column=0)
root.update_idletasks()
Thanks for any help...
Steve P
EDIT: Okay, after attempting to make use of #ebarr's example, here's what I have. The label widget updates with the count but the bargraph does not:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 2014-07-24 S. Petit
import matplotlib.pyplot as plt
from serial import Serial
import serial, socket, time, datetime, sys, struct
from datetime import datetime
import numpy as np
import shutil
import os
from random import randint
import Tkinter as tk
from multiprocessing import *
dcbSerialPort = 'COM10'
# count from 0 to infinity, writing the value to a pipe
def count(pipe,stop):
ii = 0
while not stop.is_set():
ii+=1
pipe.send(ii)
time.sleep(1)
class UpdatingGUI(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.parent = parent
self.parent_pipe, self.child_pipe = Pipe()
self.stop_event = Event()
# label to show count value
self.updating_int = tk.IntVar()
self.updating_int.set(0)
self.updating_lbl = tk.Label(self,textvariable=self.updating_int)
self.updating_lbl.pack()
# bargraph to show count value
self.area_barGraph = tk.Canvas(width=120, height=100, bg='blue')
self.bargraph = self.area_barGraph.create_polygon(10,10, (10+self.updating_int.get()),10, (10+self.updating_int.get()),20, 10,20, outline='green', fill='yellow')
self.area_barGraph.pack()
# button that will stay responsive to requests while count is on going
self.quit_btn = tk.Button(self,text="Quit",command=self.quit)
self.quit_btn.pack()
# launch count as a process
self.counter = Process(target=count,args=(self.child_pipe,self.stop_event))
self.counter.start()
# call an update method to check the pipe and update the label
self.update()
def quit(self):
self.stop_event.set()
self.parent.destroy()
def update(self):
# While the pipe has data, read and update the StringVar
while self.parent_pipe.poll():
self.updating_int.set(self.parent_pipe.recv())
# set the update method to run again in 1 seconds time
self.parent.after(1000,self.update)
def main():
root = tk.Tk()
gui = UpdatingGUI(root)
gui.pack()
root.mainloop()
# print __name__
if __name__ == "__main__":
main()
You are pretty close to a working solution. As is noted in one of the comments above, using the tkinter after will solve most of your problem.
Below is a minimal example of a separate process (running a simple counter) passing a state that can be used to update your GUI:
import Tkinter as tk
from multiprocessing import Event,Process,Pipe
from time import sleep
# count from 0 to infinity, writing the value to a pipe
def count(pipe,stop):
ii = 0
while not stop.is_set():
ii+=1
pipe.send(ii)
sleep(1)
class UpdatingGUI(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.parent = parent
self.parent_pipe, self.child_pipe = Pipe()
self.stop_event = Event()
# label to show count value
self.updating_txt = tk.StringVar()
self.updating_txt.set("Waiting...")
self.updating_lbl = tk.Label(self,textvariable=self.updating_txt)
self.updating_lbl.pack()
# button that will stay responsive to requests while count is on going
self.quit_btn = tk.Button(self,text="Quit",command=self.quit)
self.quit_btn.pack()
# launch count as a process
self.counter = Process(target=count,args=(self.child_pipe,self.stop_event))
self.counter.start()
# call an update method to check the pipe and update the label
self.update()
def quit(self):
self.stop_event.set()
self.parent.destroy()
def update(self):
# While the pipe has data, read and update the StringVar
while self.parent_pipe.poll():
self.updating_txt.set(self.parent_pipe.recv())
# set the update method to run again in 1 seconds time
self.parent.after(1000,self.update)
def main():
root = tk.Tk()
gui = UpdatingGUI(root)
gui.pack()
root.mainloop()
if __name__ == "__main__":
main()
UPDATE
In response to the updated code: You are pretty much done, the only issue is that you are only calling the bargraph creator once, whereas it needs to be added to your update function like:
def update(self):
# While the pipe has data, read and update the StringVar
while self.parent_pipe.poll():
self.updating_int.set(self.parent_pipe.recv())
dx = self.updating_int.get()
self.area_barGraph.create_polygon(10,10, (10+dx),10, (10+dx),20, 10,20, outline='green', fill='yellow')
# set the update method to run again in 1 seconds time
self.parent.after(1000,self.update)
This will ensure that every time the intVar is updated the graph is also updated appropriately.
I'm planning to write a small GUI around a numerical simulation which is why I'm now playing around with Tkinter. Simulations should be launched from GUI in seperate processes. To play around a bit I defined a function random_process that generates pairs of randn numbers (this should later be a real simulation process). As that function is meant to be launched in a seperate process, two mp.Event objects and one mp.Pipe object are passed as parameters.
The main application can use one event to request accumulated data from the process, another event is used as a "Poison Pill" to kill the "simulation" process. A pipe is then used to pass the data.
In the main application, I use Tkinter's after-function to regularly check if new data has arrived and then plot it. Starting and stopping the "simulation process" is done by buttons in the main app, the same goes for requesting data from it.
At least that was the idea, in practice the program doesn't play nice. When I click on the "go!" button that is meant to launch the simulation process, a second Tkinter window appears, identical to the main one. I don't have the slightest clue why that happens. The communication with the process doesn't work neither, no data seems to be send. When googling for a solution, I found a working example of a Tkinter program launching processes and talking to them, but I didn't find out what makes it not work in my case. Has anybody got a clue?
BTW, the OS is Windows-7.
Cheers,
Jan
import matplotlib
matplotlib.use('TkAgg')
import time
import multiprocessing as mp
import Tkinter as Tk
import numpy.random as npr
import matplotlib.figure
import matplotlib.backends.backend_tkagg as tkagg
def random_process(delay, data_request, data_in, poison):
while not poison.is_set():
time.sleep(delay)
print("Generating pair of random numbers...")
x,y = npr.randn(), npr.randn()
try:
random_process.l.append((x,y))
except:
random_process.l = [(x,y)]
if data_request.is_set():
data_request.clear()
try:
ll = len(random_process.l)
if ll > 0:
print("Sending %d pairs to main program.." % ll)
data_in.send(random_process.l)
random_process.l = []
except:
print("data requested, but none there.")
# when poison event is set, clear it:
poison.clear()
class GuiInterfaceApp:
def __init__(self, parent):
self.myParent = parent
self.previewplot_container = Tk.Frame(self.myParent)
self.f = matplotlib.figure.Figure()
self.ax = self.f.add_subplot(111)
self.preview_canvas = tkagg.FigureCanvasTkAgg(self.f, master=self.previewplot_container)
self.preview_canvas.show()
self.button_container = Tk.Frame(self.myParent)
self.hellobutton = Tk.Button(self.button_container, text="hello!")
self.hellobutton.config(command = self.printhello)
self.startbutton = Tk.Button(self.button_container, text="go!")
self.startbutton.config(command=self.run_simulation)
self.plotbutton = Tk.Button(self.button_container, text="show!")
self.plotbutton.config(command=self.request_data)
self.stopbutton = Tk.Button(self.button_container, text="stop.")
self.stopbutton.config(command=self.stop_simulation)
self.quitbutton = Tk.Button(self.button_container, text="get me outta here!")
self.quitbutton.config(command=self.quit_program)
self.previewplot_container.pack(side = Tk.TOP)
self.preview_canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
self.button_container.pack(side = Tk.BOTTOM)
self.hellobutton.pack(side = Tk.LEFT)
self.startbutton.pack(side = Tk.LEFT)
self.plotbutton.pack(side = Tk.LEFT)
self.stopbutton.pack(side = Tk.LEFT)
self.quitbutton.pack(side = Tk.LEFT)
self.simulation_running = False
self.datarequest = mp.Event()
self.DataIn, self.DataOut = mp.Pipe()
self.PoisonEvent = mp.Event()
self.p = mp.Process(target = random_process, args=(1.0, self.datarequest, self.DataIn, self.PoisonEvent))
self.l = [] # list of received pairs to plot
self.mytask_time = 100 # delay in ms between calls to self.mytask
def printhello(self):
print("hello!")
def run_simulation(self):
print("startbutton pressed.")
if not self.simulation_running:
print("starting simulation...")
self.p.start()
self.simulation_running = True # attention: no error checking
def stop_simulation(self):
print("stop button pressed.")
if self.simulation_running:
print("Sending poison pill to simulation process..")
self.PoisonEvent.set()
self.simulation_running = False
# todo: wait a short amount of time and check if simu stopped.
def request_data(self):
print("plotbutton pressed.")
if self.simulation_running:
print("requesting data from simulation process")
self.datarequest.set()
def update_plot(self):
print("update_plot called.")
if len(self.l) > 0:
print("there is data to plot.")
while len(self.l) > 0:
x,y = self.l.pop()
print("plotting point (%.2f, %.2f)" % (x,y))
self.ax.plot([x], [y], '.', color='blue')
print("drawing the hole thing..")
self.ax.draw()
else:
print("nothing to draw")
def quit_program(self):
print("quitbutton pressed.")
if self.simulation_running:
print("sending poison pill to simulation process..")
self.PoisonEvent.set()
print("quitting mainloop..")
self.myParent.quit()
print("destroying root window..")
self.myParent.destroy()
def receive_data(self):
if self.DataOut.poll():
print("receiving data..")
data = self.DataOut.recv()
self.l.append(data)
self.update_plot()
def my_tasks(self):
self.receive_data()
self.myParent.after(self.mytask_time, self.my_tasks)
return
root = Tk.Tk()
myGuiInterfaceApp = GuiInterfaceApp(root)
root.after(100, myGuiInterfaceApp.my_tasks)
root.mainloop()
Try hiding your main logic behind a test for whether the code is being run or imported.
if __name__ == "__main__":
root = Tk.Tk()
...
so i have this code:
import thread
from Tkinter import *
import random
import time
Admin=Tk()
def moveit(number):
songas=Label(Admin,text=number,bg='red')
def ji():
plad=0.0
recount=0
times=0
while 1:
plad-=0.1
recount+=1
times+=1
time.sleep(0.5)
pls=0.0
pls+=plad
if recount==4:
pls=0
plad=0.0
recount=0
songas.place(relx=pls,rely=0.7)
thread.start_new_thread(ji,())
za=random.random()
button=Button(Admin,text='Press',command=lambda:moveit(str(za)))
button.place(relx=0.2)
Admin.mainloop()
And it starts to move to the left but if you press the 'press' button again it puts some more numbers on top of the old ones.
does any one know how to erase the old numbers to make it so there are only the knew ones?
Tkinter isn't thread safe -- you can't manipulate widgets in any thread except the main one or you'll get undefined results.
You don't need threads for this. Your code adds an infinite loop, but the application already has an infinite loop (the event loop) that you can take advantage of.
If you want to move some item create a function that does two things. First, it does whatever it is you want, such as move the item. Second, it uses the standard after method to call itself again in a short amount of time (for example, half a second or 500ms). This way you let your event loop drive the animation, you don't need threads, and your UI stays responsive.
Here's an example. I doubt it does exactly what you want because I'm not certain of exactly what you want.
import Tkinter as tk
import random
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
self._running = False
self._relx = None
tk.Tk.__init__(self, *args, **kwargs)
self.pack_propagate(False)
self.configure(width=400, height=400)
self.label = tk.Label(self, text="hello, world", background="red")
self.button = tk.Button(self, text="Start", command=self.toggle)
self.button.pack(side="top")
def toggle(self):
'''toggle animation on or off'''
self._running = not self._running
if self._running:
self.button.configure(text="Stop")
self.moveit()
else:
self.button.configure(text="Start")
def moveit(self):
'''Animate the label'''
if not self._running:
# animation has been stopped
# hide the label from view.
self.label.place_forget()
if self._running:
if not self.label.winfo_viewable():
# not visible; establish future locations
self._relx = [.5, .4, .3, .2, .1, 0]
relx = self._relx.pop(0)
self._relx.append(relx)
self.label.place(relx=relx, rely=0.7)
self.after(1000, self.moveit)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
You must signal the old thread to exit somehow. It is probably easiest to perform with locks - you create a lock when creating a new thread and acquire it. And you release it when the thread is no longer needed. The thread then only needs to check in the main loop whether its lock is still locked - if it isn't it will remove the label and exit. Here the modified version of your code (replace "Remove label here" comment by suitable code):
import thread
from Tkinter import *
import random
import time
Admin=Tk()
lock = None
def moveit(number):
global lock
songas=Label(Admin,text=number,bg='red')
def ji(lock):
plad=0.0
recount=0
times=0
while 1:
plad-=0.1
recount+=1
times+=1
time.sleep(0.5)
pls=0.0
pls+=plad
if recount==4:
pls=0
plad=0.0
recount=0
songas.place(relx=pls,rely=0.7)
if not lock.locked():
# Remove label here
break
if lock:
# Signal old thread to exit
lock.release()
lock = thread.allocate_lock()
lock.acquire()
thread.start_new_thread(ji,(lock,))
za=random.random()
button=Button(Admin,text='Press',command=lambda:moveit(str(za)))
button.place(relx=0.2)
Admin.mainloop()
i'm not particularly new at python but i just thought there might be a reason for this program not working properly. i have written a similar one from which this is derived, and that still works fine. it is a program to graph the average time of a set of ping responses, to see if there is any pattern in the time over the day. the source is as follows
from Tkinter import *
import matplotlib
import time
import os, sys, threading, Queue
matplotlib.use('TkAgg')
from numpy import arange, array, sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import Tkinter
import sys
class App(object):
def __init__(self):
self.q = Queue.Queue()
self.q2 = Queue.Queue()
self.avvals=[]
self.addlist=['bbc.co.uk', 'google.co.uk', 'nhgs.co.uk', 'bing.co.uk', 'msn.com']
self.addlistlen = len(self.addlist)
self.root = Tkinter.Tk()
self.root.wm_title("Connection Speed")
self.frame = Tkinter.Frame(self.root)
self.frame.pack(side='top', expand=1, fill='both',)
self.frame2 = Tkinter.Frame(self.frame)
self.frame2.pack(side='bottom', expand=1, fill='both',)
self.f = Figure(figsize=(5,4), dpi=100)
self.a = self.f.add_subplot(111)
self.gframe = Tkinter.Frame(self.frame)
self.gframe.pack(side='top', expand=1, fill='both',)
self.canvas = FigureCanvasTkAgg(self.f, master=self.gframe)
self.canvas.show()
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
self.canvas._tkcanvas.pack(side='top', fill='both', expand=1)
self.lt = threading.Thread(target=self.loop())
def getping(self, add):
pingaling = os.popen("ping -n 2 "+str(add).strip())
sys.stdout.flush()
line = pingaling.read()
if line:
try:
line = line.split('Maximum = ')[1]
time = line.split('ms, Average')[0]
self.q.put(int(time))
except:
self.q.put(None)
def gpthread(self, *a):
t = threading.Thread(target=self.getping, args=a)
t.isDaemon = True
t.start()
def loop(self):
while 1:
for x in self.addlist:
self.gpthread(x)
while self.q.qsize<self.addlistlen:
pass
tl = []
for u in range(self.addlistlen):
temp = self.q.get()
if temp != None:
tl.append(temp)
if len(tl)>0:
self.update(sum(tl)/len(tl))
else:
self.update(None)
def update(self, val):
self.a.clear()
self.avvals.append(val)
self.a.plot(self.avvals, linewidth=0.5, color = 'b')
self.canvas.draw()
a = App()
try:
a.root.mainloop()
except:
a.root.destroy()
i probably dont need the bottom try..except but i put it in to check if it would make a difference.
i haven't had a chance to try it on another computer, but my other scripts are working fine so....
i simply can't comprehend why it freezes, stops responding, and if i exit it by any method i get a error saying
Fatal python error: PyEval NULL tstate
or somthing very similar.
now it doesn't even expand! it just goes straight to not responding!
Change
self.lt = threading.Thread(target=self.loop())
to
self.lt = threading.Thread(target=self.loop)
target=self.loop() calls the loop method before passing the result to threading.Thread.
Passing target=self.loop passes the method object to threading.Thread without calling it. This lets threading.Thread call the method in a new thread.
Here is some code which uses threads to ping some ips, and displays the average ping times in an animated matplotlib bar chart, embedded in a Tkinter window:
import Tkinter
import threading
import subprocess
import Queue
import shlex
import re
import matplotlib.pyplot as plt
import matplotlib.backends.backend_tkagg as tkagg
import atexit
import numpy as np
pingers=[]
def cleanup():
print('terminating ping subprocesses...')
for pinger in pingers:
pinger.proc.terminate()
atexit.register(cleanup)
class Pinger(threading.Thread):
def __init__(self,app,queue):
threading.Thread.__init__(self)
self.app=app
self.queue=queue
def run(self):
# One ping subprocess is started by each Pinger thread.
# The ping subprocess runs indefinitely, terminated by the cleanup function
# which is called by atexit right before the main program terminates.
ip = self.queue.get()
cmd="ping %s" % ip
self.proc = subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE)
for line in iter(self.proc.stdout.readline,''):
match=re.search('time=(.*)\s+ms',line)
if match:
avg=float(match.group(1))
self.app.update(ip,avg)
class App(object):
def __init__(self,master,ips):
self.ips=ips
self.fig = plt.Figure(figsize=(5,4), dpi=100)
self.fig.subplots_adjust(bottom=0.25)
self.ax=self.fig.add_subplot(1,1,1)
self.canvas = tkagg.FigureCanvasTkAgg(self.fig, master=master)
self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
self.canvas.show()
N=len(self.ips)
self.idx=dict(zip(self.ips,range(N)))
# I set an initial ping time of 200 just to make the initial bar chart
times=[200]*N
self.rects=self.ax.bar(range(N), times)
self.ax.set_xticks(np.arange(N)+0.8*0.5)
self.ax.set_xticklabels(self.ips, rotation=25)
def update(self,ip,avg):
# This is called by Pinger threads, each time a new ping value is obtained
print(ip,avg)
self.rects[self.idx[ip]].set_height(avg)
self.canvas.draw()
def main():
root = Tkinter.Tk()
root.wm_title("Connection Speed")
ips=['bbc.co.uk', 'google.co.uk', 'nhgs.co.uk', 'bing.co.uk', 'msn.com']
app = App(root,ips)
queue = Queue.Queue()
for ip in ips:
queue.put(ip)
# This starts one Pinger for each ip.
pinger=Pinger(app,queue)
pingers.append(pinger)
pinger.daemon=True
pinger.start()
Tkinter.mainloop()
if __name__=='__main__':
main()