Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

    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));

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);

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() ;
}

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());

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());

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?
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top