0

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?

@lvc

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

mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • When you edit your question, even if you say @person in it, it doesn't actually notify that person - common practice is to update it and then post a comment on their answer (or an @ person comment somewhere they've commented) saying you've updated it. – lvc Jun 18 '12 at 01:47
  • ... and having said that, I went and did exactly the same thing. Please see my updated answer. – lvc Jun 19 '12 at 14:53

1 Answers1

1

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 shouldn't 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 class itself 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))
lvc
  • 34,233
  • 10
  • 73
  • 98
  • thanks for this. Yes, this is a neat technique and nicely pythonic. The very slight thing that occurred to me is that it still involves creating and submitting a Runnable. The other alternative that has occurred to me is to have a (python) Thread subclass along with an encapsulated (python) Queue and a (Java) SwingWorker... works quite nicely and I have no idea of the performance gain, if any. Have put in the code above, would welcome any comments – mike rodent Jun 30 '12 at 07:17