I have a script I wrote out of modifications of helloworld for gkt, and cmd.
#!/usr/bin/python
import cmd
from gi.repository import Gtk
import threading
class GtkInterface(object):
def __init__(self):
win = Gtk.Window()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
self.window = win;
def create_button(self):
self.button = Gtk.Button(label="Click Here")
self.button.connect("clicked", self.on_button_clicked)
self.window.add(self.button)
self.window.show_all()
def on_button_clicked(self, widget):
print 'something happened'
return
class HelloWorld(cmd.Cmd):
#Simple command processor example.
prompt='>'
def __init__(self, gtk_object):
cmd.Cmd.__init__(self)
# or cmd.Cmd.__init__(self)
self.gtk_object = gtk_object
def do_greet(self, line):
print "hello"
def do_setbutton(self, line):
self.gtk_object.create_button()
def do_exit(self, line):
return True
gtk_o = GtkInterface()
hello = HelloWorld(gtk_o)
def worker(num):
"""thread worker function"""
#print 'Worker: %s' % num
hello.cmdloop()
return
def worker2(num):
Gtk.main()
threads = []
t = threading.Thread(target=worker, args=(1,))
threads.append(t)
t2 = threading.Thread(target=worker2, args=(2,))
threads.append(t2)
if __name__ == '__main__':
#HelloWorld().cmdloop()
#Gtk.main()
t.start()
t2.start()
This works. What I'd like to know is this ok? Are there issues to look out for? This is my first time trying this so there are a lot of unknowns for me. I understand that both cmd, and gtk are blocking. The Gtk.main, and cmd loops work flawlessly so far. I'm just being cautious.
My first time using threading too. When cmd gets the command to setbutton the button is set. When the button is clicked 'something happened' prints. The command line continues as if nothing out of the ordinary happened. I was really surprised at how seamless it all works. Yet I am still a little worried.
GTK has its' own threading library, and you need to be careful I think with complex applications : http://faq.pygtk.org/index.py?req=show&file=faq20.006.htp - for instance when you have threads which update your GUI indepedently of the main thread.
In your example you do have a threaded application, although in fact your entire GTK application is running in a single thread - so you are ok.
GTK+ is not thread safe and there are a few problems with the example which may cause instability. The example loads GTK+ in the main thread, runs the GTK+ main loop in a different thread and creates GTK+ widgets in yet another thread. All the GTK+ API calls should occur in the main thread with other threads communicating back to the main GUI thread by adding idle or timer callbacks. Have a read through the PyGObject threading wiki [1].
https://wiki.gnome.org/Projects/PyGObject/Threading
Related
I am testing Python threading with the following script:
import threading
class FirstThread (threading.Thread):
def run (self):
while True:
print 'first'
class SecondThread (threading.Thread):
def run (self):
while True:
print 'second'
FirstThread().start()
SecondThread().start()
This is running in Python 2.7 on Kubuntu 11.10. Ctrl+C will not kill it. I also tried adding a handler for system signals, but that did not help:
import signal
import sys
def signal_handler(signal, frame):
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
To kill the process I am killing it by PID after sending the program to the background with Ctrl+Z, which isn't being ignored. Why is Ctrl+C being ignored so persistently? How can I resolve this?
Ctrl+C terminates the main thread, but because your threads aren't in daemon mode, they keep running, and that keeps the process alive. We can make them daemons:
f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()
But then there's another problem - once the main thread has started your threads, there's nothing else for it to do. So it exits, and the threads are destroyed instantly. So let's keep the main thread alive:
import time
while True:
time.sleep(1)
Now it will keep print 'first' and 'second' until you hit Ctrl+C.
Edit: as commenters have pointed out, the daemon threads may not get a chance to clean up things like temporary files. If you need that, then catch the KeyboardInterrupt on the main thread and have it co-ordinate cleanup and shutdown. But in many cases, letting daemon threads die suddenly is probably good enough.
KeyboardInterrupt and signals are only seen by the process (ie the main thread)... Have a look at Ctrl-c i.e. KeyboardInterrupt to kill threads in python
I think it's best to call join() on your threads when you expect them to die. I've taken the liberty to make the change your loops to end (you can add whatever cleanup needs are required to there as well). The variable die is checked on each pass and when it's True, the program exits.
import threading
import time
class MyThread (threading.Thread):
die = False
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run (self):
while not self.die:
time.sleep(1)
print (self.name)
def join(self):
self.die = True
super().join()
if __name__ == '__main__':
f = MyThread('first')
f.start()
s = MyThread('second')
s.start()
try:
while True:
time.sleep(2)
except KeyboardInterrupt:
f.join()
s.join()
An improved version of #Thomas K's answer:
Defining an assistant function is_any_thread_alive() according to this gist, which can terminates the main() automatically.
Example codes:
import threading
def job1():
...
def job2():
...
def is_any_thread_alive(threads):
return True in [t.is_alive() for t in threads]
if __name__ == "__main__":
...
t1 = threading.Thread(target=job1,daemon=True)
t2 = threading.Thread(target=job2,daemon=True)
t1.start()
t2.start()
while is_any_thread_alive([t1,t2]):
time.sleep(0)
One simple 'gotcha' to beware of, are you sure CAPS LOCK isn't on?
I was running a Python script in the Thonny IDE on a Pi4. With CAPS LOCK on, Ctrl+Shift+C is passed to the keyboard buffer, not Ctrl+C.
Context
I'm creating a PySide2 tool running in Maya. The tool is executing a lot of long tasks, some modifying the scene (cleaning tasks), some creating files (exporting tasks).
Because this is a long task, I'd like to display feedback (progress bar) while it's running.
Problems
Unfortunately, so far, the whole UI does not seem to be updated during the executing.
Also, because I had odd behaviors (Maya freezing forever) in the real code, I'm guessing this is not a safe use of threads.
Example code
Here is a simplified bit of code showing where I am so far. Is this the right way to use QThread? I'm from a CG Artist background, not a professional programmer, so I'm probably misusing or misunderstanding the concepts I'm trying to use (threads, PySide...)
import time
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWidgets import *
import maya.cmds as cmds
class Application(object):
def __init__(self):
self.view = View(self)
def do_something(self, callback):
start = int(cmds.playbackOptions(q=True, min=True))
end = int(cmds.playbackOptions(q=True, max=True))
# First operation
for frame in xrange(start, end + 1):
cmds.currentTime(frame, edit=True)
# Export ...
callback(33)
time.sleep(1)
# Second operation
for frame in xrange(start, end + 1):
cmds.currentTime(frame, edit=True)
# Export ...
callback(66)
time.sleep(1)
# Third operation
for frame in xrange(start, end + 1):
cmds.currentTime(frame, edit=True)
# Export ...
callback(100)
time.sleep(1)
class View(QWidget):
def __init__(self, controller):
super(View, self).__init__()
self.controller = controller
self.thread = None
self.setLayout(QVBoxLayout())
self.progress = QLabel()
self.layout().addWidget(self.progress)
self.button = QPushButton('Do something')
self.layout().addWidget(self.button)
self.button.clicked.connect(self.do_something)
self.show()
def do_something(self):
self.thread = DoSomethingThread(self.controller)
self.thread.updated.connect(lambda progress: self.progress.setText(str(progress) + '%'))
self.thread.run()
class DoSomethingThread(QThread):
completed = Signal()
updated = Signal(int)
def __init__(self, controller, parent=None):
super(DoSomethingThread, self).__init__(parent)
self.controller = controller
def run(self):
self.controller.do_something(self.update_progress)
self.completed.emit()
def update_progress(self, progress):
self.updated.emit(int(progress))
app = Application()
Threads are difficult to use correctly in Maya Python (you can see this from the number of questions listed here)
Generally there are two hard rules to observe:
all work that touches the Maya scene (say selecting or moving an object) has to happen in the main thread
all work that touches Maya GUI also has to happen in the main thread.
"main thread" here is the thread you get when you run a script from the listener, not on you're creating for yourself
This obviously makes a lot of things hard to do. Generally a solution will involve the a controlling operation running on the main thread while other work that does not touch Maya GUI or scene objects is happening elsewhere. A thread-safe container (like a python Queue can be used to move completed work out of a worker thread into a place where the main thread can get to it safely, or you can use QT signals to safely trigger work in the main thread.... all of which is a bit tricky if you're not far along in your programming career.
The good news is -- if all the work you want to do in Maya is in the scene you aren't losing much by not having threads. Unless the work is basically non-Maya work -- like grabbing data of the web using an HTTP request, or writing a non-Maya file to disk, or something else that does not deal with Maya-specific data -- adding threads won't get you any additional performance. It looks like your example is advancing the time line, doing work, and then trying to update a PySide GUI. For that you don't really need threads at all (you also don't need a separate QApplication -- Maya is already a QApplication)
Here's a really dumb example.
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import maya.cmds as cmds
class DumbWindow(QWidget):
def __init__(self):
super(DumbWindow, self).__init__()
#get the maya app
maya_app = QCoreApplication.instance()
# find the main window for a parent
for widget in maya_app.topLevelWidgets():
if 'TmainWindow' in widget.metaObject().className():
self.setParent(widget)
break
self.setWindowTitle("Hello World")
self.setWindowFlags(Qt.Window)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
start_button = QPushButton('Start', self)
stop_button = QPushButton('Stop', self)
self.layout.addWidget(start_button)
self.layout.addWidget(stop_button)
self.should_cancel = False
self.operation = None
self.job = None
# hook up the buttons
start_button.clicked.connect(self.start)
stop_button.clicked.connect(self.stop)
def start(self):
'''kicks off the work in 'this_is_the_work'''
self.operation = self.this_is_the_work()
self.should_cancel = False
self.job = cmds.scriptJob(ie=self.this_makes_it_tick)
def stop(self):
''' cancel before the next step'''
self.should_cancel = True
def this_is_the_work(self):
print "--- started ---"
for frame in range(100):
cmds.currentTime(frame, edit=True)
yield "advanced", frame
print "--- DONE ----"
def bail(self):
self.operation = None
def kill_my_job():
cmds.scriptJob(k=self.job)
print "job killed"
cmds.scriptJob(ie = kill_my_job, runOnce=True)
def this_makes_it_tick(self):
'''
this is called whenever Maya is idle and thie
'''
# not started yet
if not self.operation:
return
# user asked to cancel
if self.should_cancel:
print "cancelling"
self.bail()
return
try:
# do one step. Here's where you can update the
# gui if you need to
result = next(self.operation)
print result
# example GUI update
self.setWindowTitle("frame %i" % result[-1])
except StopIteration:
# no more stpes, we're done
print "completed"
self.bail()
except Exception as e:
print "oops", e
self.bail()
test = DumbWindow()
test.show()
Hitting start creates a maya scriptJob that will try to run whatever operation is in the function called this_is_the_work(). It will run to the next yield statement and then check to make sure the user hasn't asked to cancel the job. Between yields Maya will be busy (just as it would if you entered some lines in the listener) but if you're interacting with Maya when a yield comes up, the script will wait for you instead. This allows for safe user interaction without a separate thread, though of course it's not as smooth as a completely separate thread either.
You'll notice that this kicks off a second scriptJob in the bail() method -- that's because a scriptJob can't kill itself, so we create another one which will run during the next idle event and kill the one we don't want.
This trick is basically how most of the Maya's MEL-based UI works under the hood -- if you run cmds.scriptJob(lj=True) in the listener you'll usually see a lot of scriptJobs that represent UI elements keeping track of things.
I have a Python application with a Tkinter GUI. In the app, the user invokes a long-running background process (implemented as a Thread from Python's threading module). I'm having trouble killing the background thread if I quit the program before it's complete. My code works if I quit the application by closing the root window via the 'X' at its top corner, but not if I quit from the top-level menu bar (i.e. Python > Quit or Ctrl+Q). Since most applications use the latter, I'd really like to make that work.
Right now, I kill the background thread with code that looks like this:
class BackgroundCallFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.background_call = BackgroundCall()
self.background_call.start()
class BackgroundCall(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._stop_req = threading.Event()
def run(self):
for i in range(1,100000):
if self._stop_req.is_set():
return
else:
# do stuff
def stop(self):
self._stop_req.set();
def main():
def kill_all_threads():
if child.background_call is not None:
child.background_call.stop()
child.background_call.join()
root.destroy()
root = Tk()
root.wm_protocol ("WM_DELETE_WINDOW", kill_all_threads)
child = BackgroundCallFrame()
child.pack()
root.mainloop()
if __name__ == '__main__':
main()
How can I make this work if I quit the program without explicitly closing the root window first?
I believe my problem is that kill_all_threads() is not called when I invoke Ctrl+Q, because any print statements I add to it never appear in the console.
Usually you will want to kill a thread by setting a variable in that thread to signal it to stop working. In the run method of your thread you will need to periodically check that variable to know when to exit the run method. Here's an example...
import threading
import time
class WorkerThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.do_stop = False
def run(self):
while True:
time.sleep(.5)
print 'I am running'
if self.do_stop:
return # exit loop and function thus exiting thread
t = WorkerThread()
t.start()
time.sleep(4) # sleep while our thread runs and prints to the command line
print 'setting do_stop to True'
t.do_stop = True # signal to thread to stop
t.join() # wait for thread to exit
print 'all done'
...outputs...
I am running
I am running
I am running
I am running
I am running
I am running
I am running
setting do_stop to True
I am running
all done
My problem is with os.system. Until it finishes formatting, GUI freezes, and I can't fix it.
class ImageDialog(QtGui.QMainWindow):
def __init__(self):
QtGui.QDialog.__init__(self)
self.ui = uic.loadUi("Recursos/main.ui",self)
self.connect(self.ui.proteger_Button, QtCore.SIGNAL("clicked()"),self,
QtCore.SLOT("protec()"))
#QtCore.pyqtSlot()
def protec(self):
self.USB = "G:"
comando = "format %s /fs:ntfs /q /v:test /y" %(self.USB)
os.system(comando)`
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = ImageDialog()
window.show()
sys.exit(app.exec_())
The simplest fix in your case is to add:
from threading import Thread
to your imports list, and then:
#QtCore.pyqtSlot()
def protec(self):
self.USB = "G:"
comando = "format %s /fs:ntfs /q /v:test /y" %(self.USB)
t = Thread(target = lambda: os.system(comando))
t.start()
This way the GUI thread will not get stuck waiting for the external process to finish.
You could probably remember the t's value and manage it in some way, so the number of threads running won't increase without limit, in case the external commands will hang, or run for a long time.
Note that it is not a good way to handle external processes. subprocess module is recommended for this.
I am testing Python threading with the following script:
import threading
class FirstThread (threading.Thread):
def run (self):
while True:
print 'first'
class SecondThread (threading.Thread):
def run (self):
while True:
print 'second'
FirstThread().start()
SecondThread().start()
This is running in Python 2.7 on Kubuntu 11.10. Ctrl+C will not kill it. I also tried adding a handler for system signals, but that did not help:
import signal
import sys
def signal_handler(signal, frame):
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
To kill the process I am killing it by PID after sending the program to the background with Ctrl+Z, which isn't being ignored. Why is Ctrl+C being ignored so persistently? How can I resolve this?
Ctrl+C terminates the main thread, but because your threads aren't in daemon mode, they keep running, and that keeps the process alive. We can make them daemons:
f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()
But then there's another problem - once the main thread has started your threads, there's nothing else for it to do. So it exits, and the threads are destroyed instantly. So let's keep the main thread alive:
import time
while True:
time.sleep(1)
Now it will keep print 'first' and 'second' until you hit Ctrl+C.
Edit: as commenters have pointed out, the daemon threads may not get a chance to clean up things like temporary files. If you need that, then catch the KeyboardInterrupt on the main thread and have it co-ordinate cleanup and shutdown. But in many cases, letting daemon threads die suddenly is probably good enough.
KeyboardInterrupt and signals are only seen by the process (ie the main thread)... Have a look at Ctrl-c i.e. KeyboardInterrupt to kill threads in python
I think it's best to call join() on your threads when you expect them to die. I've taken the liberty to make the change your loops to end (you can add whatever cleanup needs are required to there as well). The variable die is checked on each pass and when it's True, the program exits.
import threading
import time
class MyThread (threading.Thread):
die = False
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run (self):
while not self.die:
time.sleep(1)
print (self.name)
def join(self):
self.die = True
super().join()
if __name__ == '__main__':
f = MyThread('first')
f.start()
s = MyThread('second')
s.start()
try:
while True:
time.sleep(2)
except KeyboardInterrupt:
f.join()
s.join()
An improved version of #Thomas K's answer:
Defining an assistant function is_any_thread_alive() according to this gist, which can terminates the main() automatically.
Example codes:
import threading
def job1():
...
def job2():
...
def is_any_thread_alive(threads):
return True in [t.is_alive() for t in threads]
if __name__ == "__main__":
...
t1 = threading.Thread(target=job1,daemon=True)
t2 = threading.Thread(target=job2,daemon=True)
t1.start()
t2.start()
while is_any_thread_alive([t1,t2]):
time.sleep(0)
One simple 'gotcha' to beware of, are you sure CAPS LOCK isn't on?
I was running a Python script in the Thonny IDE on a Pi4. With CAPS LOCK on, Ctrl+Shift+C is passed to the keyboard buffer, not Ctrl+C.