Basically I have a program that starts a new thread, then tries to do stuff in this new thread. However, the new thread seems to get stuck until the main thread reaches the end of the program.
here is a working example of the problem I'm experiencing. it seems that gtk.main() has to be there for this problem to occur. if I use something like input() instead, the problem doesn't appear.
import threading,sys
class MyThread(threading.Thread):
def run(self):
import time
print('before')
time.sleep(3)
print('after')
MyThread().start()
from gi.repository import Gtk as gtk
class My_window:
def __init__(self):
self.window = gtk.Window()
self.window.connect("delete_event", gtk.main_quit)
self.button = gtk.Button("Hello World")
self.window.add(self.button)
self.button.show()
self.window.show()
My_window()
gtk.main()
What should happens: The window appears, the word before appears, and 3 seconds later the word after appears
What really happens: The window appears, the word before appears, then nothing happens. When you close the window, the word after appears. (As if somehow reaching the end of the program makes all the running threads that were frozen before, running again)
Things I've tried:
Replaced time.sleep with other functions, like a terminal command, or a busy loop. I get the same results
I tried recreating the problem without the window and other extra stuff, but couldn't. so I don't know where this problem resides, so I gave all the background info that I could.
I tried using sys.stdout.flush() to make sure that the text was actually showing up in the command line when it should. It was.
Does anyone have any suggestions as to what the problem might be?
I'm using python 3 (and gtk for the window), and would prefer my program to be compatible on all major os's
edit:
I tried putting a print('starting') right before gtk.main(). the output showed before then starting then after. I'm thinking that calling gtk.main freezes all threads, and when gtk.main ends, then the threads resume.
edit 2:
never-mind, in my original program, the thread was created while gtk.main is running, but there could still be something in gtk.main that is freezing the threads, that is being called every once in a while.
I managed to reproduce this, and spent ages trying to work out why it was failing, until I came across this FAQ entry, which suggested putting...
import gobject
gobject.threads_init()
...at the top of the code, which seems to fix the problem.
My guess is that after calling gtk.main(), GTK is holding on to Python's Global Interpreter Lock, which is a bit naughty for default behavior, but at least there's a way to prevent it.
Note that background threads are not able to manipulate GTK objects directly, so you'll have to have them pass messages to the main thread to do that instead.
if th is the thread object you created, you should th.setDaemon(1) before starting it, otherwise the main thread won't exit without the thread you started exit first.
Related
I have been trying to use tkinter to make a gui to select some excel files and sheets from those files.
I have a lot of experience in python, but I'd probably say a novice at tkinter.
The code I have written to select the files is shown below (typos are likely because I cannot access the internet on the machine these files are on, so I am typing it up here).
My question is about mainloop(), the update functions, and after(). I had mainloop() at the end of my code, but my program wouldn't terminate (i.e. the terminal would remain) after it did what it does. So I removed the mainloop() and now it functions perfectly without any mainloop(), update(), or after() calls.
I don't really understand this and would really like to. I get that mainloop() stops the code from progressing until the root closes, but I thought nothing would show up without mainloop(), and this code does wait for the user to close the windows before continuing.
I was also wondering if I do not have mainloop (or the like), the code still closes fine whether or not I have root.destroy() at the end of the App class (commented in the code). I don't get that either.
This information would help me make better and correct code in the future and hopefully improve this one. FYI, I have searched for the answer to this everywhere and could not find one, at least one I could understand. Thanks a bunch.
This is as minimal as I can think of for this code.
Code (Edited from original post):
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, parent):
self.root = parent
self.root.withdraw()
tl = tk.Toplevel(parent)
b = ttk.Button(tl, text="Test widget")
b.grid()
tl.wait_window()
# This line does not change how the code functions at all
#self.root.destroy()
def run(self):
# Whether or not this is a function in the class or called globally doesn't matter.
self.root.mainloop()
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
# This is the mainloop() call. If I include it, the program does not close after
# running the code in App.__init__. Without it, it runs perfectly.
# app.run()
mainloop enters an event listening loop for your tk.Tk() application.
You should create only one object with Tk() in every tkinter application.
Now this loop will "globally" listen for GUI events and will call your registered callbacks (event handlers). Code before the mainloop call will run but nothing will be shown or updated in the screen until you call it. No event (user input, mouse movement, keyboard and others) will be queued and responded to before that. But the widgets are created and configured, just not shown yet. If you call root.update() without ever entering the mainloop you will eventually see the window flash and disappear because everything is running, and you force window refresh, but then suddenly the program ends.
So configure your GUI and in the end always run root.mainloop()
Now when you call wait_window, you are creating a local event loop (and blocking the mainloop if it was running). For a perfect explanation of wait_window by Brian see here
What happened to you is that your app is happily running with your local event loop, but without mainloop. That would be a strange thing to do. Even if you are making as Brian explains, a modal window, you want to "return" to the mainloop after the window closes.
As for the after method it just registers a callback function to run after your choosen time, or after the mainloop becames idle, with after_idle (nothing more to refresh, no events in the queue..., also think as soon as possible). And if you want to re-run that function again in the same way it should re-register with after or after_idle before returning.
So always use your mainloop() ending call - or should I say event loop starting call :)
Edit:
Also, don't do things in your code that are slow, or that may block the loops, such as calling sleep or any other blocking function. That will freeze the GUI, until the block ends.
I've been fighting with Tkinter for a while now and have exhausted most the resources I have for referencing this. I've found a couple similar topics here but none quite bring me to where I need to be.
I've got a long running (not long actually, it only takes 10-12 secs) python script that silently install an application using subprocess from the CLI. Subprocess worked and it successfully installed the application, however, the GUI locks (i.e hangs/freeze) after the execution (and the program no longer run the succeeding code). I know I have to use threading here but I've already tried using it to no avail.
As part of my learning process, I cloned a repo from Github and modify it to run on Windows (since it only run in MAC platform) and planning to extend it, and this is the part where I got stucked. This is my first time to use TKinter and I apologize if I have missed something stupid or not asked the question in the right way. Hope you can help me and thank you in advance for the assistance.
Code can be found on this link.
You have defined a button
installButton = Button(bottomFrame, text=installButtonTxt,
command=on_install_thread, width=9)
with command handler
def on_install_thread():
...
loop_thread = threading.Thread(target=on_install_button_active,
args=['button', 'model', itemSelectCount])
and the target for thread on_install_button_active.
Then
def on_install_button_active(button, model, selectcount):
...
# Reset install status
installStatus = 'complete'
# Remove Cancel Button
cancelButton.destroy()
# Activate Install/Done button and menus
installButton.configure(state=NORMAL)
# menuControl('normal')
refreshGui(mainWindow)
It looks like there is code at the end of on_install_button_active that involves Tkinter
widgets. Calling tkinter methods from other threads seems to be unreliable, for example 1.
It is possible to define virtual events
def renderMainWindow():
...
mainWindow.bind('<<InstallComplete>>', on_install_complete)
def on_install_complete():
cancelButton.destroy()
installButton.configure(state=NORMAL)
def on_install_button_active(button, model, selectcount):
...
# Reset install status
installStatus = 'complete'
mainWindow.event_generate('<<InstallComplete>>', when='tail')
If there are other calls in that thread that involve Tkinter widgets, it might be better to remove them.
I was able to resolved problems with my GUI by using mtTkinter and by referring to this post.
So I have a Tkinter application that I use at work. I wrote another Tkinter application that I wanted to call from the main Tkinter application. I know that Tkinter isn't 'thread safe', but I'm not 100% sure what that means. Does it mean that it can 'work', but it isn't guaranteed to work as written? Or should it just not work at all?
The reason I ask is because when I run the code below(self.thread_easy_imaging() is triggered by a filemenu option), it works just fine. So far I haven't encountered any issues and both GUIs are operational so far as I can tell. Is this because I'm using subprocess.call to call the script? I'm having a hard time wrapping my head around this because I know you can't use thread in a Tkinter app to do something like run a function while still being able to use buttons in the GUI, but for some reason when I use a thread to call the script with subprocess.call it does it just fine. When I wrote it, I assumed it wouldn't work, but for some odd reason it does. Here is an example of the code I'm using:
def thread_easy_imaging(self):
thread.start_new_thread(self.start_easy_imaging, ('EASY-IMAGING-1', 0))
def start_easy_imaging(self, thread_name, delay):
time.sleep(delay) #have to have args for some reason? I just did a delay of 0 seconds so I could use the tuple.
subprocess.call(['c:/python27/python.exe', 'EasyImaging.py'])
self.thread_easy_imaging()
I have only used this on a windows machine, but I'm guessing that it should work on any OS.
Edit: The scripts don't have to interact at all, I just want to add my other GUI application into the filemenu so I can call it as a separate application. If needed, I'll just use a Toplevel widget to recreate the application I want to call because it won't muck up the main thread at all.
Thanks in advance!
So i am running into a major issue. I am currently trying to use multi-processing/sub-processing to run files next to my Tkinter application, however, as soon as I run the process, the GUI freezes until I finish the process. Is there any way to get around this? I have also looked at other questions on SO but to no avail (I found one suggesting root.update() however that is not working as expected.
Note: I have not included GUI elements, as I have made basic programs to try this (only a couple of lines) and get the same problem. It also may be worth noting that I am running windows.
code (taken out of context):
def run_file(self):
self.root.update()
sub_process=subprocess.call(self.sub_proc_args)
process=multiprocessing.Process(target=self.run_file())
process.start()
By doing self.run_file() you are calling run_file before multiprocessing can use it. You need to use target=self.run_file (note, without parentheses).
I have found the problem (other then the target issue that BrenBarn pointed out). Here is my fix:
(i took out the multiprocessing.)
def run_file(self):
sub_process=subprocess.Popen(self.sub_proc_args) #Open subprocess
self.root.update() #Update GUI
self.run_file() #Initiate sub_process
I have found that this is the fix because using call, when you execute it, it must return a return value, which makes Tkinter not continue its mainloop. This is fixed by using Popen.
I worked with a bunch of examples I Googled, and looked at a bunch of questions about threading here on stackoverflow, but I still can't seem to get it.
Here is some sample code I am working with:
class Debugger(QTextBrowser):
def __init__(self, parent):
super(Debugger, self).__init__(parent)
sys.stdout = self
def write(self, text):
self.moveCursor(QTextCursor.End)
self.textCursor().insertText(text)
Basically, I am trying to catch any 'print' statements or other forms of writing to the stdoutstream. But I can't get it to print them one by one as they arrive. Instead, it waits until whatever process that prints the strings is finished, and then begins to log the information. So how do I thread this properly?
Edit: Reduced code to bare minimum.
I suspect you are running it all in one thread?
While the other code is running the Qt mainloop is not being runned, therefore it is not processing events (drawing on the screen, taking input, etc.). So while the textbox internally is being updated, it is not being painted on the screen, until your task completes, and then the window is redrawn.
The best solution is to run the process that is doing output (and taking some time) in a separate thread.
A quick-n-dirty solution is to manually iterate the Qt mainloop whenever you have written something to the textbox, see IPython iterate main loop manually?.