This module aims to help with two mutually related tasks:
All properly designed GUI applications execute potentially long running operations in separate threads to prevent the interface from becoming unresponsive. This, on the other hand, creates new challenges because often, the code in the background thread needs to access the GUI. Java 6 incorporated the SwingWorker class to help with this, but even this solution is quite clumsy when compared to what the flexible Python language can accomplish. Jython-Swingutils has a mechanism, inspired by the Twisted framework, that makes switching between threads almost seamless.
To run background tasks, you first need to create a thread pool. The TaskExecutor class is basically Java’s ThreadPoolExecutor with some sugar topping that lets it cooperate with Python better.
from swingutils.threads.threadpool import TaskExecutor def hello(name): print 'Hello, %s' % name executor = TaskExecutor() executor.runBackground(hello, 'world')
This will print “Hello, world” from a background thread. The functionally equivalent version using the decorator form is this:
from swingutils.threads.threadpool import TaskExecutor executor = TaskExecutor() @executor.backgroundTask def hello(name): print 'Hello, %s' % name hello('world')
When you need to manipulate live GUI components from background threads, you need to make sure that any code doing so executes in the Event Dispatch Thread (EDT) to avoid thread safety issues. There are several different ways you can do this, depending on whether you want the calling thread to block or not, and how to deal with calls already originating from the EDT.
from swingutils.threads.swing import callSwing def fillInExchangeRate(): rate = fetchExchangeRate('USD', 'EUR') callSwing(rateField.setValue, rate)
The following table lists the differences between the various different GUI call mechanisms:
|Function||Blocks calling thread||Blocks EDT if called from within|
So we have the means to execute code in background threads and to execute GUI code from those threads. This is however not the best we can do with Python. If you have a complex event chain that requires bouncing between background and GUI threads, things can get quite messy.
Enter the @inlineCallbacks decorator. Decorating a function with this allows the execution to “adjourn” while waiting for the background task to complete, freeing the EDT to attend to other tasks while waiting. Observe:
from swingutils.threads.defer import inlineCallbacks from swingutils.threads.threadpool import TaskExecutor executor = TaskExecutor() @inlineCallbacks def fillInExchangeRates(rates): for rateField, curr1, curr2 in rates: rate = yield executor.runBackground(fetchExchangeRate, curr1, curr2) rateField.setValue(rate)
The code wrapped by @inlineCallbacks always run in the EDT. What happens here is that it runs until a request is made to fetch an exchange rate in a background thread. At that point, the execution is “adjourned” and the EDT returns to processing its own queue. When fetchExchangeRate() returns, it causes a new task to be pushed to the EDT processing queue that resumes execution of the fillInExchangeRates() function.
Technically this was implemented using Python’s generator mechanism, which unfortunately adds a few restrictions:
The yield statement can be safely used when calling functions from an @inlineCallbacks decorated function. Doing so ensures proper handling of any returned AsyncTokens.