
Question:
In Python one is used to doing
def runTaskInNonEDT():
pass
tRunTask = threading.Thread( target = runTaskInNonEDT )
tRunTask.start()
In Jython, I find that if I want to submit a method to the EDT I have to go
def makeRunnableClass():
class RunnableClass( Runnable ):
def run( rSelf ):
pass
return RunnableClass
SwingUtilities.invokeAndWait( makeRunnableClass()() )
obviously you then have all the attendant questions about passing parameters, etc. I was just wondering if there might be a snappier, more Pythonesque way of submitting a method to the EDT?
<em><strong>@lvc</strong></em>
thanks... yes in fact I get that... in fact the idiom
def makeSthgClass():
class SthgClass():
pass
return SthgClass
is one I use habitually just to stop cluttering up the namespace with one-shot subclassed instances' classnames.
I have in fact got sthg to lighten the task
def runToMessageTree( self, method, *args, **kvargs ):
if SwingUtilities.isEventDispatchThread():
method( *args, **kvargs )
else:
def makeRunnableClass():
class RunnableClass( Runnable ):
def run( self ):
method( *args, **kvargs )
return RunnableClass
SwingUtilities.invokeAndWait( makeRunnableClass()() )
so you can go
def doSthg():
pass
self.runToMessageTree( doSthg )
... but there's nothing satisfyingly Pythonic about it.
later:
class EDTWorkerThread( WorkerThread ):
def __init__( ewt_self, name ):
super( EDTWorkerThread, ewt_self ).__init__( name )
class EDTWorker( SwingWorker ):
def doInBackground(self ):
check_event_thread( False )
while True:
method_call_elements = ewt_self.input_queue.get()
if method_call_elements is None: # "poison pill"
break
self.super__publish( [ method_call_elements ])
ewt_self.input_queue.task_done()
return
def process( self, chunks ):
check_event_thread( True )
for chunk in chunks:
assert type( chunk ) is list
assert chunk # i.e. must have at least one element!
# check that first item is callable
assert hasattr( chunk[ 0 ], "__call__" )
method_call_elements = chunk
method_args = method_call_elements[ 1 : ]
method_call_elements[ 0 ]( *method_args )
ewt_self.input_queue.task_done()
ewt_self.swing_worker = EDTWorker()
def run( self ):
self.swing_worker.execute()
ẀorkerThread is a very simple, classic python idiom:
class WorkerThread( threading.Thread ):
def __init__( self, *args, **kvargs ):
threading.Thread.__init__( self, *args, **kvargs )
self.input_queue = Queue()
def send( self, item ):
assert type( item ) is list
assert item # i.e. must have at least one element!
# check that first item is callable
assert hasattr( item[ 0 ], "__call__" )
self.input_queue.put( item )
def close( self ):
self.input_queue.put( None )
self.input_queue.join()
def run( self ):
while True:
method_call_elements = self.input_queue.get()
if method_call_elements is None: # "poison pill"
break
method_args = method_call_elements[ 1 : ]
method_call_elements[ 0 ]( *method_args )
self.input_queue.task_done()
self.input_queue.task_done()
return
so you submit a method followed by optional args ... and this method then ends up being run in the EDT, using the args in question. No Runnables have to be created...
Of course the other possibility is to subclass from SwingWorker... then you wouldn't have this slightly troubling "double-queue" arrangement (i.e. WorkerThread Queue, and the EDT's own queue, which delivers to process())... but then you have to have a rather inelegant loop (using sleep()) in doInBackground...
Would be interested in people's views
Answer1:The major thing to realise is that SwingUtilities.invokeAndWait
expects an instance of a single-method interface because Java doesn't have first-class functions. That bit isn't avoidable without using something other than SwingUtilities
, with a more Pythonic interface, for this functionality.
If your heart is set on using that particular API, you can still avoid having the wrapper function. Just do:
class RunnableClass(Runnable):
def run(self):
pass
SwingUtilities.invokeAndWait(RunnableClass())
The only reason for using the wrapper function is to be able to use pass a function in to invoke in run
using closures; you can still do this by passing the function into RunnableClass.__init__
and storing it:
class RunnableClass(Runnable):
def __init__(self, func):
self._func = func
def run(self):
self._func()
Note that func
<em>shouldn't</em> take self
as a first parameter - since its an attribute on the instance rather than on the class, it doesn't get treated as a method.
Per your edit - the point of passing func
into RunnableClass.__init__
is that it doesn't need to be a one-shot subclass anymore - you don't need one subclass of Runnable
for every func you're going to run, just one instance of RunnableClass
. The <em>class itself</em> is a generic adapter from the Python idiom to the Java one, so you don't need a function around it to do the same job.
This means your runToMessageTree
can look like this:
def runToMessageTree(self, method, *args, **kwargs):
if SwingUtilities.isEventDispatchThread():
method(*args, **kwargs)
else:
SwingUtilities.invokeAndWait(RunnableClass(method, *args, **kwargs))