20131205

Thread-safe Java Swing initialization in Jython

Java's Swing API is not thread-safe: initialization and manipulation of Swing components must be done only from the event dispatch thread (EDT). Programmers must use verbose idioms to initialize Swing components on the EDT.

Jython is a Python interpreter which runs on top of Java. It provides the functionality of Python, plus a "pythonic" way to interact with existing Java APIs.

Consider initializing a Swing application in Jython. According to specifications, even constructing Swing components on the Main thread like this can be unsafe.
 from javax.swing import *  
 jf = JFrame("Hi")  
 jf.contentPane.add(JButton("does nothing"))  
 # more unsafe initialization here  
 # ...  
 jf.pack()  
 jf.visible = 1  
This code might seem OK, or it might crash intermittently and only on some platforms. The only way to conform to specifications is to move this initialization onto the event dispatch thread. The classic Java idiom to achieve this is to wrap the initialization code in a new subclass of Runnable, and pass this Runnable to the event dispatch thread to be executed. In Jython, this looks something like
 from javax.swing import *  
 from java.awt import EventQueue  
 from java.lang import Runnable  
 class initializeApp(Runnable):  
   def run(self):  
     global jf  
     jf = JFrame("Hi")  
     jf.contentPane.add(JButton("does nothing"))  
     # more unsafe initialization here  
     jf.pack()  
     jf.visible = 1  
 EventQueue.invokeAndWait(initializeApp())  
This is a little verbose in Python, and would be significantly verbose in Java, to the point where programmers would prefer some sort of macro to simplify the process. The Python language feature of "decorators" provides this. In Python, decorators are functions which enhance or modify other functions. They are implemented as a Python function that accepts and returns functions as an argument. Here is a Python decorator that will transform any function into a version which is thread-safe for Swing:
 from java.awt import EventQueue  
 from java.lang import Runnable  
 def EDTsafe(function):  
     def safe(*args,**kwargs):  
         if EventQueue.isDispatchThread():  
             return function(*args,**kwargs)  
         else:  
             class foo(Runnable):  
                 def run(self):  
                     self.result = function(*args,**kwargs)  
             f = foo()  
             EventQueue.invokeAndWait(f)  
             return f.result  
     safe.__name__ = function.__name__  
     safe.__doc__ = function.__doc__  
     safe.__dict__.update(function.__dict__)  
     return safe  
Using this decorator, we can do thread-safe Swing initialization succinctly
 from javax.swing import *  
 @EDTsafe  
 def initializeApp():  
     global jf  
     jf = JFrame("Hi")  
     jf.contentPane.add(JButton("does nothing"))  
     # more unsafe initialization here  
     jf.pack()  
     jf.visible = 1  
 initializeApp()  
There is also the option of using the decorator in one-line lambda functions, for example
 EDTsafe( lambda: jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) )()  

No comments:

Post a Comment