[BangPypers] Poor man's LWP with PyQt

Sirtaj Singh Kang sirtaj at sirtaj.net
Sat May 9 06:31:43 CEST 2009

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):
                 call_iter = microfn(qobj, *call_args, **call_kwargs)
             except StopIteration, endex:

             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
             for itidx in xrange(self.iterations):
         except StopIteration, sex:
         except Exception, ex:
             print "QMICRO: Unhandled exception:", ex



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):
	def beer(n):
		for x in xrange(n):
			print x, "bottles of beer on the wall"

	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.


-------------- next part --------------
A non-text attachment was scrubbed...
Name: qmp.py
Type: text/x-python-script
Size: 2748 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/bangpypers/attachments/20090509/372bb12e/attachment.bin>
-------------- next part --------------

More information about the BangPypers mailing list