I want to spawn another process to display an error message asynchronously while the rest of the application continues.
I'm using the multiprocessing module in Python 2.6 to create the process and I'm trying to display the window with TKinter.
This code worked okay on Windows, but running it on Linux the TKinter window does not appear if I call 'showerror("MyApp Error", "Something bad happened.")'. It does appear if I run it in the same process by calling showerrorprocess directly. Given this, it seems TKinter is working properly. I can print to the console and do other things from processes spawned by multiprocessing, so it seems to be working too.
They just don't seem to work together. Do I need to do something special to allow spawned subprocesses to create windows?
from multiprocessing import Process
from Tkinter import Tk, Text, END, BOTH, DISABLED
import sys
import traceback
def showerrorprocess(title,text):
"""Pop up a window with the given title and text. The
text will be selectable (so you can copy it to the
clipboard) but not editable. Returns when the
window is closed."""
root = Tk()
root.title(title)
text_box = Text(root,width=80,height=15)
text_box.pack(fill=BOTH)
text_box.insert(END,text)
text_box.config(state=DISABLED)
def quit():
root.destroy()
root.quit()
root.protocol("WM_DELETE_WINDOW", quit)
root.mainloop()
def showerror(title,text):
"""Pop up a window with the given title and text. The
text will be selectable (so you can copy it to the
clipboard) but not editable. Runs asynchronously in
a new child process."""
process = Process(target=showerrorprocess,args=(title,text))
process.start()
Edit
The issue seems to be that TKinter was imported by the parent process, and "inherited" into the child process, but somehow its state is inextricably linked to the parent process and it cannot work in the child. So long as you make sure not to import TKinter before you spawn the child process, it will work because then it is the child process that is importing it for the first time.
This discussion could be helpful.
Here's some sample problems I found:
While the multiprocessing module follows threading closely, it's definitely not an exact match. One example: since parameters to a
process must be pickleable, I had to go through a lot of code
changes to avoid passing Tkinter objects since these aren't
pickleable. This doesn't occur with the threading module.
process.terminate() doesn't really work after the first attempt. The second or third attempt simply hangs the interpreter, probably
because data structures are corrupted (mentioned in the API, but this
is little consolation).
Maybe calling the shell command xhost + before calling your program from that same shell will work?
I am guessing your problem lies with the X-server.
Related
MyButton1 =Button(master, text='Quit',bg="grey",width=20,
command=master.quit)
MyButton1.place(x=200, y=100)
MyButton2 =Button(master, text='Propagate', bg="grey",width=20,
command=mainmethod)
MyButton2.place(x=1000, y=100)
master.geometry("1500x1500")
master.mainloop( )
In the above code after pressing propagate button mainmethod is invoking..
I wrote my logic in main method where this method alone taking 2minutes to execute in the mean time GUI going unresponsive state for few min and later displaying all my required output on text box i inserted
whether any away to avoid the unresponsive issue apart from using multi threading
and i am looking such that after pressing propagate button button should disabled and window should not go unresponsive and display text.insert statements continuously which i added in main method ?????
To prevent hanging, you need to separate the calculations in the mainmethod from Tkinter's main loop by executing them in different threads. However, threading system in Python is not that well-developed as in other languages (AFAIK) because of GIL (Global Interpreter Lock), but there is an alternative - using processes just like threads. This is possible with multiprocessing library.
In order to just prevent hanging, you could create another function
from multiprocessing import Process
def mainmethodLaunch():
global mainmethodProcess
mainmethodProcess = Process(target=mainmethod)
mainmethodProcess.start()
And bind this function to MyButton2 instead of mainmethod itself.
Docs: https://docs.python.org/2/library/multiprocessing.html#the-process-class
You can see p.join in the example. join method will cause your main process to wait for the other one to complete, which you don't want.
So when you press the button, mainmethodLaunch function will be invoked, and it will create another process executing mainmethod. mainmethodLaunch function's own run duration should be insignificant. Due to usage of another process, Tkinter window will not hang. However, if you do just this, you will not be able to interact with mainmethod process in any kind while it will be working.
In order to let these processes communicate with each other, you could use pipes (https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes)
I guess the example is quite clear.
In order to receive some data from the mainmethod process over time, you will have to poll the parent_conn once a little time, let's say, second. This can be achieved with Tkinter's after method
(tkinter: how to use after method)
IMPORTANT NOTE: when using multiprocessing, you MUST initialize the program in if __name__ == '__main__': block. I mean, there should be no doing-something code outside functions and this block, no doing-something code with zero indent.
This is because multiprocessing is going to fork the same Python executable file, and it will have to distinguish the main process from the forked one, and not do initializing stuff in the forked one.
Check twice if you have done that because if you make such a mistake, it can cost you hanging of not just Tkinter window, but the whole system :)
Because the process will be going to fork itself endlessly, consuming all RAM you have, regardless of how much you have.
I'm making a simple window to hold my NetworkX graph, and I want this GUI component to be separate from my logic. So what I'm trying to achieve is to make a function that makes the window, initializes the graph, starts the mainloop in a Thread and returns the gui reference. I've done this exact thing without networkX and canvas involved, and it works great, but now this doesn't work for some reason. What I have is:
def getNewGraphWindow():
root = Tk()
app = GraphUI(root)
root.mainloop()
#mainThread = Thread(target=root.mainloop)
#mainThread.start()
return app
So this code works perfectly, it creates the window, draws the graph and all that, but obviously I don't get the "app" reference cause its stuck in the mainloop. But if I replace that code with the 2 commented out lines, to simply run root.mainloop() in its own thread, all the code runs, but the window doesn't respond and the graph is not drawn. What's wrong?
You have to run the mainloop in the thread where you created the Tcl interpreter, according to the _tkinter source code:
The Tcl interpreter is only valid in the thread that created it, and
all Tk activity must happen in this thread, also. That means that the
mainloop must be invoked in the thread that created the interpreter.
So you'll need to run the mainloop in the main thread, and do whatever else you need to do in a background thread. You actually might be able to run the mainloop in a background thread, as long as you also create the Tk element in that same thread.
I need to run some simple function in multi-threading with a Tkinter GUI, so I've tried mtTkinter.
Everything works fine except for a particular: even if I just start the GUI and then I close it without touching nothing some thread keeps running.
In other words; I have this code:
from Tkinter import *
root = Tk()
#simple GUI code with buttons, labels, text and scrollbars widget
...
...
root.mainloop()
If I run this code the GUI appears and when I close it this python script ends successfully.
Now if I replace Tkinter with mtTkinter
from mtTkinter import *
root = Tk()
#simple GUI code with buttons, labels, text and scrollbars widget
...
...
root.mainloop()
the GUI appears once again, but if I close it there is still some thread from mtTkinter that keeps running!
Any help would be apprecied, thank you in advance and sorry for my bad english!
I ran into a similar problem for my application (https://github.com/joecole889/spam-filter). After some investigation, I realized that when I close my application Tkinter (or possibly Matplotlib) uses a threading._DummyThread instance to delete one of the widgets. I have a Matplotlib graph in a Tkinter canvas widget in my application. In any case, it looks like an “image delete” event is added to the event queue and mtTkinter blocks waiting for a response on the responseQueue that never comes.
I was able to fix the problem by allowing events from instances of threading._DummyThread to run without going through the queue infrastructure of mtTkinter. That is, I changed:
if threading.currentThread() == self._tk._creationThread:
to
if (threading.currentThread() == self._tk._creationThread) or \
isinstance(threading.currentThread(), threading._DummyThread) :
Things seem to be working for me now...hope this helps!
I've "resolved" not using it. mTkinter seems to be a bit buggy.
This is an old topic, but I don't see where it was even closed. I have a python application using 4 threads using the 'theading' module and MtTkinter.
I was having similar problems with MtTkinter. The application worked but would not close. I have searched and tried quite a few solutions, none worked. For my application, using queues would have been a chore.
Here is what I did. Its not elegant, but it worked. Its pretty ruthless.
cleanup():`
pidx = os.getpid()
cmd1 = "kill" + " " + str(pidx)
if __name__ == "__main__":
os.system(cmd1)
The following code hangs without doing anything in Python 3.2.2 in Linux:
import tkinter
from multiprocessing import Process
def f():
root = tkinter.Tk()
label = tkinter.Label(root)
label.pack()
root.mainloop()
p = Process(target=f)
p.start()
The only information I have found about this problem is issue 5527, in which it is noted that the problem is with tkinter being imported before the process is forked, and that it can be fixed by importing tkinter inside the function f, and that the problem occurs in Linux but not Solaris.
What exactly is causing this problem, and is it a bug? Is there any workaround other than to import tkinter locally everywhere I need it (which isn't very Pythonic)? Do any other modules have similar issues with multiprocessing?
My suspicion is that the problem has to do with the connection to the X server (usually a socket). If this is created before the process is fork()-ed, the child process inherits this connection. But if it tries to use it, the X server gets confused.
After a cursory look at at Tkinter.py, it looks like maybe calling the NoDefaultRoot function before starting the process might be useful. It all depends on when the connection to the X server is made.
Otherwise importing Tkinter after the fork seems the way to go.
As of September 2013, there are some additional comments on the bug report that give more insight into what the actual problem is.
http://bugs.python.org/issue5527#msg194848
http://bugs.python.org/issue5527#msg195480
Based on the above, I'm guessing something like the following is happening: Tkinter is not thread safe, so (for whatever reason) Tkinter wants to know which thread is the main thread. Tkinter assumes that the main thread when the Tkinter module is loaded will also be the main thread for program execution. When you fork or multiprocess after loading Tkinter, this assumption is broken. (For example, after a fork, the remembered main thread is in the parent, not the child.)
If you can get a thread started, just do root.mainloop() after it and it should work fine.
I have a Python script which uses Tkinter for the GUI. My little script should create a Toplevel widget every X seconds. When I run my code, the first Toplevel widget is created successfully, but when it tries to create a second one the program crashes.
What I am doing is using the after method to call the function startCounting every 5 seconds alongside root's mainloop. Every time this function is called, I append a Toplevel widget object into a list and start a new thread which hopefully will be running the new mainloop.
I would be very grateful if someone could figure this problem out. By the way, this is just a little script that I am currently using to solve my problem, which is preventing me from going on with my real school project.
The code:
import threading,thread
from Tkinter import *
def startCounting():
global root
global topLevelList
global classInstance
topLevelList.append (Toplevel())
topLevelList[len(topLevelList)-1].title("Child")
classInstance.append(mainLoopThread(topLevelList[len(topLevelList)-1]))
root.after(5000,startCounting)
class mainLoopThread(threading.Thread):
def __init__(self,toplevelW):
self.toplevelW = toplevelW
threading.Thread.__init__(self)
self.start()
def run(self):
self.toplevelW.mainloop()
global classInstance
classInstance = []
global topLevelList
topLevelList = []
global root
root = Tk()
root.title("Main")
startCounting()
root.mainloop()
Tkinter is designed to run from the main thread, only. See the docs:
Just run all UI code in the main
thread, and let the writers write to a
Queue object; e.g.
...and a substantial example follows, showing secondary threads writing requests to a queue, and the main loop being exclusively responsible for all direct interactions with Tk.
Many objects and subsystems don't like receiving requests from multiple various threads, and in the case of GUI toolkit it's not rare to need specfically to use the main thread only.
The right Python architecture for this issue is always to devote a thread (the main one, if one must) to serving the finicky object or subsystem; every other thread requiring interaction with said subsystem or object must them obtain it by queueing requests to the dedicated thread (and possibly waiting on a "return queue" for results, if results are required as a consequence of some request). This is also a very sound Python architecture for general-purpose threading (and I expound on it at length in "Python in a Nutshell", but that's another subject;-).
Tkinter has issues dealing with input from multiple threads, I use mtTkinter instead, you won't need to change any code and everything will work fine. Just import mtTkinter instead of Tkinter.
You can get it here:
http://tkinter.unpythonic.net/wiki/mtTkinter
Is there a reason you want (or think you need) one event loop per toplevel window? A single event loop is able to handle dozens (if not hundreds or thousands) of toplevel windows. And, as has been pointed out in another answer, you can't run this event loop in a separate thread.
So, to fix your code you need to only use a single event loop, and have that run in the main thread.