9 May 06:31
Poor man's LWP with PyQt
Hi all, I've been trying to find the path of least resistance to using cooperative multitasking in PyQt apps. There are a bunch of different options out there, including Twisted (The initial inqternet.py Qt support I wrote became the basis of the current Qt reactor, and it works great if you are using Twisted) and Kamaelia/Axon (I had a stab at implementing Qt event loop support for that - available here: http://sirtaj.net/projects/axonqt.py). However most of these approaches require you to commit to frameworks that are likely to shape the implementation of the rest of your app. The two recommended ways to doing this sort of thing in Qt/C++ are: 1) use QTimer with a timeout of 0 to call some function. 2) In a long-running loop, call processEvents to allow other events to be processed to keep the GUI interactive. ...and that's all she wrote, since C++ doesn't really allow many options besides multithreading. With the yield keyword, however, we can get the same kind of cooperative multitasking that we had back in 1991 with Visual Basic 1.0 (yay!). This is the approach that Axon uses. Using the QTimer method above, a handful of lines of code gets us this in PyQt without having to use a larger framework: --------------------------------------------------------------- from PyQt4.QtCore import QObject, SIGNAL def qmicro(iterations=500): '''Qt fire-and-forget microprocess decorator. ''' def wrap_qmicro(microfn): def call_qmicro(qobj, *call_args, **call_kwargs): try: call_iter = microfn(qobj, *call_args, **call_kwargs) except StopIteration, endex: return except: raise return QtMicroProcess(qobj, call_iter.next, iterations) return call_qmicro return wrap_qmicro class QtMicroProcess(QObject): '''A single running microprocess, scheduled in the event loop using timer events until completed or error. ''' def __init__(self, parent, next_fn, iterations): QObject.__init__(self, parent) self.next_fn = next_fn self.iterations = iterations self.timer_id = self.startTimer(0) def timerEvent(self, tev): next_fn = self.next_fn try: for itidx in xrange(self.iterations): next_fn() return except StopIteration, sex: pass except Exception, ex: print "QMICRO: Unhandled exception:", ex try: self.killTimer(self.timer_id) finally: self.deleteLater() -------------------------------------------------------------- Now we can create "fire and forget" LWPs methods that can "yield" to the Qt event loop by simply using the @qmicro decorator: class MyApp(QObject): @qmicro() def beer(n): for x in xrange(n): print x, "bottles of beer on the wall" yield def some_regular_method(): beer(99) # returns immediately (a slightly more fleshed out example is in the attached file) Note that this is a deliberately simplistic implementation that has various limitations, eg the method has to be a method of a QObject subclass, and there is no builtin way to get feedback when the LWP exits. Still, it is a convenient bit of code you can drop into your PyQt project when you want to do some background work while allowing the rest of the app to continue relatively unaffected, while sidestepping the issues that come up when calling Qt code from multiple threads. Hope someone finds this useful and any feedback appreciated. -Taj.
_______________________________________________ KDE-india mailing list KDE-india@... https://mail.kde.org/mailman/listinfo/kde-india
RSS Feed