views:

107

answers:

7

There are many cases where thread A requires a value that must be computed on thread B. (Most commonly, B == EDT.) Consider this example:

String host;
SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        host = JOptionPane.showInputDialog("Enter host name: ");
    }
});
openConnection(host);

Of course, this doesn't compile, because the anonymous inner class isn't allowed to write to host. What's the simplest, cleanest way to get this to work? I've included the ways I know of below.

A: 

Elegant? Use an atomic variable

final AtomicReference<String> host = new AtomicReference<String>();
SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        host.set(JOptionPane.showInputDialog("Enter host name: "));
    }
});
openConnection(host.get());
Matt McHenry
This one is only incorrect due to synchronization issues (how do you know host[0] will be set by the time it's referenced? (answer, it probably won't be, with the user drooling away at the console...)
andersoj
@andersoj: I know it will be set because I'm using invokeAndWait() rather than invokeLater(). (I assume you mean host.get() rather than host[0].)
Matt McHenry
@Matt McHenry: You know that assuming everything goes to plan. If the thread gets lost, encounters an exception, etc... who knows?
andersoj
A: 

Verbose: Use an inner class

class HostGetter implements Runnable{
    volatile String host;
    public void run() {
        host = JOptionPane.showInputDialog("Enter host name: ");
    }
}
HostGetter hg = new HostGetter();
SwingUtilities.invokeAndWait(hg);
openConnection(hg.host);
Matt McHenry
This one at least gets you the right answer, but it doesn't let you do anything in the interim.
andersoj
A: 

Hack: Use an array

final String[] host = new String[1];
SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        host[0] = JOptionPane.showInputDialog("Enter host name: ");
    }
});
openConnection(host[0]); //maybe not guaranteed to be visible by the memory model?
Matt McHenry
incorrect due to visibility *and* synchronization issues (how do you know host[0] will be set by the time it's referenced? (answer, it probably won't be, with the user drooling away at the console...)
andersoj
+2  A: 

No:

Use a Future<T> and possibly Callable<T> and an ExecutorService. A Future is basically an exact programmatic expression of what you want: a promise of a future answer, and the ability to block until the answer is available. Future also automatically wraps and presents for you an entire, complicated farrago of potential concurrency nightmares and complexities into a few clearly defined exceptions. This is a good thing because it forces you to deal with them, when a roll-your-own solution would likely never reveal them except in some aberrant, difficult to diagnose behavior.

public void askForAnAnswer() throws TimeoutException, InterruptedException, ExecutionException
{
  Future<String> theAnswerF = getMeAnAnswer();
  String theAnswer = theAnswerF.get();

}

public Future<String> getMeAnAnswer()
{
  Future<String> promise = null;
  // spin off thread/executor, whatever.
  SwingUtilities.invokeAndWait(new Runnable() {
  public void run() {
    host = JOptionPane.showInputDialog("Enter host name: ");
  }
 });
 // ... to wrap a Future around this.

  return promise;
}

For your specific case, you can probably build on a SwingWorker which implements Future. Rather than duplicate, please take a look at this SO question.

andersoj
+1 -- this is truly the right way to handle this in Java. They added Future/Callable specifically to deal with Runnables that return things.
Mark E
A: 
    final SynchronousQueue<String> host = new SynchronousQueue<String>();

    SwingUtilities.invokeAndWait(new Runnable() {

        public void run() {
            host.add(JOptionPane.showInputDialog("Enter host name: "));
        }

    });

    openConnection(host.poll(1, TimeUnit.SECONDS));
yawn
A: 

I'll recommend to create a class to handle this, samples below:

class SyncUserData implements Runnable {
    private String value ;

    public void run() {
        value = JOptionPane.showInputDialog("Enter host name: ") ;
    }

    public String getValue() {
        return value ;
    }
}
// Using an instance of the class launch the popup and get the data.
String host;
SyncUserData syncData = new SyncUserData() ;
SwingUtilities.invokeAndWait(syncData);
host = syncData.getValue() ;

I'll extend this approach by making the class abstract and using generics to allow any type of values to be return.

abstract class SyncUserDataGeneric<Type> implements Runnable {
    private Type value ;
    public void run() {
        value = process();
    }
    public Type getValue() {
        return value ;
    }

    public abstract Type process() ;
}

String host;
SyncUserDataGeneric<String> doHostnameGen ;

doHostnameGen = new SyncUserDataGeneric<String>() {
    public String process() {
        return JOptionPane.showInputDialog("Enter host name: ");
    }
};

host = doHostnameGen.getValue() ;

EDIT: Checks if running from the EventDispatchThread.

if (SwingUtilities.isEventDispatchThread()) {
    host = doHostnameGen.process() ;
} else {
    SwingUtilities.invokeAndWait(doHostnameGen) ;
    host = doHostnameGen.getValue() ;
}
XecP277
Doesn't SyncUserData[Generic].value need to be volatile to guarantee visibility?
Matt McHenry
No in this case, invokeAndWait guarantees the main thread will wait for the SyncUserDataGeneric to return, no locking needed. Edit for case where the code is running from an EventDispatcherThread (fired by an ActionLister).
XecP277
A: 

Please note: the author doesn't like this answer, it's just the "on the nose" answer to the specific question.

If you're only looking for a wait to minimally modify the above code so it works, you're dealing with the inner-class-can-only-refer-to-finals problem. Carve out a named inner class instead of an anonymous one and create a String host field in that class. Pass that instance to invokeAndWait(). But this is still icky, in my humble opinion, and far inferior to the Future<> approach I cited above.

class FooWidget implements Runnable() {
  AtomicReference<String> host = new AtomicReference<String>(null);
  @Override
  public void run() {
    host.set(JOptionPane.showInputDialog("Enter host name: "));
  }
}

...

FooWidget foo = new FooWidget();
SwingUtilities.invokeAndWait(foo);
if (foo.host.get() == null) { throw new SomethingWentWrongException(); }
openConnection(foo.host.get());
andersoj
Doesn't FooWidget.host need to be volatile to guarantee visibility?
Matt McHenry
Yes, or any of a number of other solutions... For this and other reasons, please see my other answer for a cleaner, more robust approach. See my update, I suppose.
andersoj