Tcl_AsyncDelete Error. Unable to terminate Tk - python

I am using Tkinter in a ROS node to create a GUI and publish the scale values to another ROS Node. I have accomplished this. The problem comes when I try to close this GUI and rerun the node. The log message that I get is as follows:
Exception RuntimeError: 'main thread is not in main loop' in <bound method DoubleVar.__del__ of <Tkinter.DoubleVar instance at 0x7f19ea0c3ab8>> ignored
Tcl_AsyncDelete: async handler deleted by the wrong thread
Aborted (core dumped)
According to this, I think I will have to terminate Tk from its own thread. But I do not know how to do this. My code is as follows:
#!/usr/bin/env python
import rospy
from std_msgs.msg import Float64MultiArray
from Tkinter import *
from calibration_camera_lidar.msg import Euler_val
import tkMessageBox
class slider():
def __init__(self):
rospy.loginfo("init")
rospy.init_node('slider', anonymous=True, disable_signals=True)
self.spub = rospy.Publisher('Slider_values', Euler_val, queue_size=10)
self.final_ev = Euler_val()
self.listener()
def listener(self):
rospy.Subscriber("Euler_values", Float64MultiArray, self.callback)
rospy.spin()
def callback(self, data):
self.eulerval = list(data.data)
self.final_ev.Euler_angles = [self.eulerval[0], self.eulerval[1], self.eulerval[2]]
self.spub.publish(self.final_ev)
rospy.loginfo(self.final_ev)
self.slider_value()
def callback_exit(self):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
self.root.destroy()
self.root.quit()
rospy.signal_shutdown("shutdown")
def slider_value(self):
self.root = Tk()
self.root.title("fine tune")
self.root.protocol("WM_DELETE_WINDOW", self.callback_exit)
self.y_var = DoubleVar()
self.y_scale = Scale( self.root, from_=self.eulerval[0]-1, to=self.eulerval[0]+1, length=300, label="yaw", resolution=0.0000000000001, variable = self.y_var, orient=HORIZONTAL, command=self.pub_y)
self.y_scale.set(self.eulerval[0])
self.y_scale.pack(anchor=CENTER)
self.label = Label(self.root)
self.label.pack()
self.root.mainloop()
def pub_y(self, val_y):
self.eulerval[0] = float(self.y_scale.get())
self.final_ev.Euler_angles = [self.eulerval[0], self.eulerval[1], self.eulerval[2]]
self.spub.publish(self.final_ev)
rospy.loginfo(self.final_ev)
if __name__ == '__main__':
try:
slider()
except:
rospy.loginfo("Node terminated.")
I would be grateful if you could help. Thanks!

The problem is that rospy is internally multithreaded yet Tk is very keen on only being used from a single thread. (Technically, it's possible to use Tk from multiple threads — by appropriate quarantining of window objects and so on — but it's really tricky to get right and you probably don't want that.)
The easiest approach in general is to make two classes, one that just handles Tk (with incoming and outgoing messages all queued) and the other which does the bridging into the rest of the code. Then, when you want the Tk GUI to appear you run a thread that just does that and then talk to that thread just by its queues. Which sounds like a lot more work, but you can't defeat Tk's internal awareness of threads other than by keeping it strictly on one thread.
However, it might be enough to change the shutdown sequence a bit to be like this.
def callback_exit(self):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
self.root.destroy()
rospy.signal_shutdown("shutdown")
sys.exit(0)
Assuming that you're in the correct thread. If not, you'll need a direct os._exit(0) instead and that's considered dangerous for good reason (yet it might be necessary).

Related

What is the main thread and loop in Python? [duplicate]

When I call
self.client = ThreadedClient()
in my Python program, I get the error
"RuntimeError: main thread is not in main loop"
I have already done some googling, but I am making an error somehow ... Can someone please help me out?
Full error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 530, in __bootstrap_inner
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 483, in run
File "/Users/Wim/Bird Swarm/bird_swarm.py", line 156, in workerGuiThread
self.root.after(200, self.workerGuiThread)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 501, in after
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1098, in _register
RuntimeError: main thread is not in main loop
Classes:
class ThreadedClient(object):
def __init__(self):
self.queue = Queue.Queue( )
self.gui = GuiPart(self.queue, self.endApplication)
self.root = self.gui.getRoot()
self.running = True
self.GuiThread = threading.Thread(target=self.workerGuiThread)
self.GuiThread.start()
def workerGuiThread(self):
while self.running:
self.root.after(200, self.workerGuiThread)
self.gui.processIncoming( )
def endApplication(self):
self.running = False
def tc_TekenVogel(self,vogel):
self.queue.put(vogel)
class GuiPart(object):
def __init__(self, queue, endCommand):
self.queue = queue
self.root = Tkinter.Tk()
Tkinter.Canvas(self.root,width=g_groottescherm,height=g_groottescherm).pack()
Tkinter.Button(self.root, text="Move 1 tick", command=self.doSomething).pack()
self.vogelcords = {} #register of bird and their corresponding coordinates
def getRoot(self):
return self.root
def doSomething():
pass #button action
def processIncoming(self):
while self.queue.qsize( ):
try:
msg = self.queue.get(0)
try:
vogel = msg
l = vogel.geeflocatie()
if self.vogelcords.has_key(vogel):
cirkel = self.vogelcords[vogel]
self.gcanvas.coords(cirkel,l.geefx()-g_groottevogel,l.geefy()-g_groottevogel,l.geefx()+g_groottevogel,l.geefy()+g_groottevogel)
else:
cirkel = self.gcanvas.create_oval(l.geefx()-g_groottevogel,l.geefy()-g_groottevogel,l.geefx()+g_groottevogel,l.geefy()+g_groottevogel,fill='red',outline='black',width=1)
self.vogelcords[vogel] = cirkel
self.gcanvas.update()
except:
print('Failed, was van het type %' % type(msg))
except Queue.Empty:
pass
You're running your main GUI loop in a thread besides the main thread. You cannot do this.
The docs mention offhandedly in a few places that Tkinter is not quite thread safe, but as far as I know, never quite come out and say that you can only talk to Tk from the main thread. The reason is that the truth is somewhat complicated. Tkinter itself is thread-safe, but it's hard to use in a multithreaded way. The closest to official documentation on this seems to be this page:
Q. Is there an alternative to Tkinter that is thread safe?
Tkinter?
Just run all UI code in the main thread, and let the writers write to a Queue object…
(The sample code given isn't great, but it's enough to figure out what they're suggesting and do things properly.)
There actually is a thread-safe alternative to Tkinter, mtTkinter. And its docs actually explain the situation pretty well:
Although Tkinter is technically thread-safe (assuming Tk is built with --enable-threads), practically speaking there are still problems when used in multithreaded Python applications. The problems stem from the fact that the _tkinter module attempts to gain control of the main thread via a polling technique when processing calls from other threads.
I believe this is exactly what you're seeing: your Tkinter code in Thread-1 is trying to peek into the main thread to find the main loop, and it's not there.
So, here are some options:
Do what the Tkinter docs recommend and use TkInter from the main thread. Possibly by moving your current main thread code into a worker thread.
If you're using some other library that wants to take over the main thread (e.g., twisted), it may have a way to integrate with Tkinter, in which case you should use that.
Use mkTkinter to solve the problem.
Also, while I didn't find any exact duplicates of this question, there are a number of related questions on SO. See this question, this answer, and many more for more information.
I know this is late, but I set my thread to a Daemon, and no exception was raised:
t = threading.Thread(target=your_func)
t.setDaemon(True)
t.start()
I found a way to solve it.
it might look like a joke but you just should add
plt.switch_backend('agg')
Since all this did help my problem but did not solve it completely here is an additional thing to keep in mind:
In my case I started off importing the pyplot library in many threads and using it there. After moving all the library calls to my main thread I still got that error.
I did get rid of it by removing all import statements of that library in other files used in other threads. Even if they did not use the library the same error was caused by it.
from tkinter import *
from threading import Thread
from time import sleep
from random import randint
class GUI():
def __init__(self):
self.root = Tk()
self.root.geometry("200x200")
self.btn = Button(self.root,text="lauch")
self.btn.pack(expand=True)
self.btn.config(command=self.action)
def run(self):
self.root.mainloop()
def add(self,string,buffer):
while self.txt:
msg = str(randint(1,100))+string+"\n"
self.txt.insert(END,msg)
sleep(0.5)
def reset_lbl(self):
self.txt = None
self.second.destroy()
def action(self):
self.second = Toplevel()
self.second.geometry("100x100")
self.txt = Text(self.second)
self.txt.pack(expand=True,fill="both")
self.t = Thread(target=self.add,args=("new",None))
self.t.setDaemon(True)
self.t.start()
self.second.protocol("WM_DELETE_WINDOW",self.reset_lbl)
a = GUI()
a.run()
maybe this example would help someone.
Write it at the end:
root.mainloop()
Of course, in place of root should be the name of your Tk object if it is not root.
You cannot modify your main GIU from another thread
you need to send event to the main GUI in order to avoid exceptions
Use window.write_event_value instead, this method allows you to send events from your threads
you can take a look a this too: window.perform_long_operation
I know this question was asked a long time ago, but I wanted to tell you how I solved it. In my case, I have a program that sends and receives messages through the serial port and uses the TKinter library.
If I do:
while (True):
#more code here
window.update_idletasks()
window.update()
The code crashes when a thread tries to access a tkinter function.
But, if I do this:
window.mainloop()
All the threads execute normaly.
Hope this helps someone.

Destroying a Toplevel in a thread locks up root

I have a program I've been writing that began as a helper function for me to find a certain report on a shared drive based on some information in that report. I decided to give it a GUI so I can distribute it to other employees, and have ran into several errors on my first attempt to implement tkinter and threading.
I'm aware of the old adage "I had one problem, then I used threads, now I have two problems." The thread did, at least, solve the first problem -- so now on to the second....
My watered down code is:
class GetReport(threading.Thread):
def __init__(self,root):
threading.Thread.__init__(self)
# this is just a hack to get the StringVar in the new thread, HELP!
self.date = root.getvar('date')
self.store = root.getvar('store')
self.report = root.getvar('report')
# this is just a hack to get the StringVar in the new thread, HELP!
self.top = Toplevel(root)
ttk.Label(self.top,text="Fooing the Bars into Bazes").pack()
self.top.withdraw()
def run(self):
self.top.deiconify()
# a function call that takes a long time
self.top.destroy() #this crashes the program
def main():
root = Tk()
date,store,report = StringVar(),StringVar(),StringVar()
#####
## labels and Entries go here that define and modify those StringVar
#####
def launchThread(rpt):
report.set(rpt)
# this is just a hack to get the StringVar in the new thread, HELP!
root.setvar('date',date.get())
root.setvar('store',store.get())
root.setvar('report',report.get())
# this is just a hack to get the StringVar in the new thread, HELP!
reportgetter = GetReport(root)
reportgetter.start()
ttk.Button(root,text="Lottery Summary",
command=lambda: launchThread('L')).grid(row=1,column=3)
root.mainloop()
My expected output is for root to open and populate with Labels, Entries, and Buttons (some of which are hidden in this example). Each button will pull data from the Entries and send them to the launchThread function, which will create a new thread to perform the foos and the bars needed to grab the paperwork I need.
That thread will launch a Toplevel basically just informing the user that it's working on it. When it's done, the Toplevel will close and the paperwork I requested will open (I'm using ShellExecute to open a .pdf) while the Thread exits (since it exits its run function)
What's ACTUALLY happening is that the thread will launch its Toplevel, the paperwork will open, then Python will become non-responsive and need to be "end processed".
As far as I know you cannot use Threading to alter any GUI elements. Such as destroying a Toplevel window.
Any Tkinter code needs to be done in the main loop of your program.
Tkinter cannot accept any commands from threads other than the main thread, so launching a TopLevel in a thread will fail by design since it cannot access the Tk in the other thread. To get around this, use the .is_alive method of threads.
def GetReport(threading.Thread):
def __init__(self,text):
self.text = text
super().__init__()
def run(self):
# do some stuff that takes a long time
# to the text you're given as input
def main():
root = Tk()
text = StringVar()
def callbackFunc(text):
top = Toplevel(root)
ttk.Label(top,text="I'm working here!").pack()
thread = GetReport(text)
thread.start()
while thread.is_alive():
root.update() # this keeps the GUI updating
top.destroy() # only when thread dies.
e_text = ttk.Entry(root,textvariable=text).pack()
ttk.Button(root,text="Frobnicate!",
command = lambda: callbackFunc(text.get())).pack()

Running a Tkinter form in a separate thread

I have written a short module that can be passed an image and simply creates a Tkinter window and displays it. The problem that I am having is that even when I instantiate and call the method that displays the image in a separate thread, the main program will not continue until the Tkinter window is closed.
Here is my module:
import Image, ImageTk
import Tkinter
class Viewer(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
def show(self,img):
self.to_display = ImageTk.PhotoImage(img)
self.label_image = Tkinter.Label(self,image=self.to_display)
self.label_image.grid(column = 0, row = 0, sticky = "NSEW")
self.mainloop()
It seems to work fine, except when I call it from my test program like the one below, it will not seem to allow my test program to continue, even when started in a different thread.
import Image
from viewer import Viewer
import threading
def showimage(im):
view = Viewer(None)
view.show(im)
if __name__ == "__main__":
im = Image.open("gaben.jpg")
t = threading.Thread(showimage(im))
t.start()
print "Program keeps going..."
I think that perhaps my problem is that I should be creating a new thread within the module itself, but I was wanting to just try and keep it simple, as I am new to Python.
Anyway, thanks in advance for any assistance.
edit: To clarity, I am just trying to make a module that will display an image in a Tkinter window, so that I can use this module any time I want to display an image. The problem that I am having is that any time a program uses this module, it cannot resume until the Tkinter window is closed.
Tkinter isn't thread safe, and the general consensus is that Tkinter doesn't work in a non-main thread. If you rewrite your code so that Tkinter runs in the main thread, you can have your workers run in other threads.
The main caveat is that the workers cannot interact with the Tkinter widgets. They will have to write data to a queue, and your main GUI thread will have to poll that queue.
If all you're doing is showing images, you probably don't need threading at all. Threading is only useful when you have a long running process that would otherwise block the GUI. Tkinter can easily handle hundreds of images and windows without breaking a sweat.
From your comments it sound's like you do not need a GUI at all. Just write the image to disk and call an external viewer.
On most systems it should be possible to launch the default viewer using something like this:
import subprocess
subprocess.Popen("yourimage.png")
From what I can tell, Tkinter doesn't like playing in other threads. See this post...I Need a little help with Python, Tkinter and threading
The work around is to create a (possibly hidden) toplevel in your main thread, spawn a separate thread to open images, etc - and use a shared queue to send messages back to the Tk thread.
Are you required to use Tkinter for your project? I like Tkinter. It's "quick and dirty." - but there are (many) cases where other GUI kits are the way to go.
I have tried to run tkinter from a separate thread, not a good idea, it freezes.
There is one solution that worked. Run the gui in the main thread, and send events to the main gui. This is similar example, it just shows a label.
import Tkinter as t
global root;
root = t.Tk()
root.title("Control center")
root.mainloop()
def new_window(*args):
global root
print "new window"
window = t.Toplevel(root)
label = t.Label(window, text="my new window")
label.pack(side="top", fill="both", padx=10, pady=10)
window.mainloop()
root.bind("<<newwin>>",new_window)
#this can be run in another thread
root.event_generate("<<newwin>>",when="tail")

Using Python Tk as a front-end for threaded code

I know that Python’s Tk interface has some problems when using threads, and I’ve already run into problems with it. My idea is now to use a Queue.Queue to somehow pass events to the Tk mainloop, similarly to the following example.
from Tkinter import *
import Queue
import time
# this queue will be filled by other threads
queue = Queue.Queue()
queue.put("Helloooo!")
queue.put("hi there, everyone!")
class Application(Frame):
def update(self):
while True:
try:
text = queue.get(False)
self.hi_there["text"] = text
time.sleep(3)
except Queue.Empty:
self.quit()
def create_widgets(self):
self.hi_there = Label(self)
self.hi_there["text"] = "Hello"
self.hi_there.pack({"side": "left"})
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets()
root = Tk()
app = Application(master=root)
app.update()
app.mainloop()
Of course, I must not call update myself (this will execute everything before the UI is even shown) but need Tk to handle that during its mainloop.
Is there any foolproof way to accomplish that with Tk which will not break under certain circumstances? Or should I just resort to Qt for that?
As a general rule of thumb a GUI application should never, ever call sleep, should never have an infinite loop (except for the event loop), and you should never call 'update'. The only exception to never calling update is that is is ok only when you truly understand why you should not.
Create a method that does two things: check the queue, and then use 'after' to call itself after some small period of time. Then, call this method once at the beginning of your program just before starting the event loop, after all other initialization has taken place.
For a working example of such a function, see How to create a timer using tkinter?

Is there a way to wrap a tkinter GUI in a class that can be created and interacted with from another object? (*without* hanging in mainloop)

Is there a way to wrap a tkinter GUI in a class that can be created and interacted with from another object? For this to be really useful, the class has to work so mainloop or its equivalent doesn't hold the application. If so, can someone point me to a working example?
For context, what I'm trying to do is make a SimpleUI class that I can use in any application to allow it to display information or register functions to be executed on button or key presses. So any threading, queues, etc. I would like to have hidden in the SimpleUI class.
Based on what I've gathered from reading around, the answer is No without re-implementing mainloop. Rather, the GUI should be the main application which farms out work through one method or another. However, this would make any application with tkinter (perhaps other GUIs as well?) feel like the tail is wagging the dog. Hopefully I have misunderstood what I have beeing reading.
I know this may seem like a repost of this and other similar questions but I can't comment on those and the answers seem to be doing the opposite of what I want to do. In addition to that question, I've found bits and pieces of related code in many places, but I have not been able to put them together. If there is a way, I'll learn Python threads or whatever to make it work.
I'm using Python 3.1 if it makes any difference.
Example of how I would like it to work.
ui = SimpleUI()
ui.hide()
ui.show()
ui.add_frame...
ui.add_button...
ui.register_function(button, function)
Is this what you're looking for?
#The threading module allows you to subclass it's thread class to create
#and run a thread, in this case we will be starting SimpleUI in a seperate thread
import threading
from Tkinter import *
def printfunction():
print "ButtonPress"
class NewClass:
def __init__(self):
self.ui = SimpleUI()
self.ui.add_frame("frame1")
self.ui.add_button("button1","frame1")
self.ui.register_function("button1",printfunction)
self.ui.start()
#self.ui = Threader().start()
def PrintSuccess(self):
print "Success!"
class SimpleUI:
def __init__(self):
self.root = Tk()
self.frames = {}
self.buttons = {}
def start(gui):
class Threader(threading.Thread):
def run(self):
gui.root.mainloop()
Threader().start()
def hide(self):
self.root.withdraw()
if(raw_input("press enter to show GUI: ")==""):self.show()
def show(self):
self.root.update()
self.root.deiconify()
def add_frame(self,name):
tmp = Frame(self.root)
tmp.pack()
self.frames[name] = tmp
def add_button(self,name,frame):
tmp = Button(self.frames[frame])
tmp.pack()
self.buttons[name] = tmp
def register_function(self,button,function):
self.buttons[button].config(command=function)
NC = NewClass()
NC.PrintSuccess()

Categories