3

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.

Matt McHenry
  • 20,009
  • 8
  • 65
  • 64

7 Answers7

2

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.

Community
  • 1
  • 1
andersoj
  • 22,406
  • 7
  • 62
  • 73
  • 1
    +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 Elliot Oct 21 '10 at 03:00
  • I'm trying to follow this to its logical conclusion, and have produced way more code than I think ought to be necessary. One thing I'm struggling with is, if SwingUtilities.invokeAndWait() throws and InterruptedException, how do I know whether my task actually completed on the EDT? Maybe that deserves its own question ... – Matt McHenry Nov 05 '10 at 20:52
  • s/and InterruptedException/an InterrputedException/ – Matt McHenry Nov 06 '10 at 14:54
  • Ah, here's a way to get around that. And substituting in a Callable for a Runnable in the method argument yields, I think, basically what I was looking for: http://blog.palantirtech.com/2008/02/21/invokeandnotwaiting/ – Matt McHenry Nov 08 '10 at 15:51
1
    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
  • 8,014
  • 7
  • 29
  • 34
  • This is basically the same as the accepted answer on the very similar question http://stackoverflow.com/questions/9397480/java-swing-application-how-to-get-data-from-the-gui-thread-to-another-thread?rq=1, and the more I think about it the more I like it. – Matt McHenry Feb 19 '14 at 01:49
0

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
  • 20,009
  • 8
  • 65
  • 64
0

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
  • 390
  • 1
  • 7
  • Doesn't SyncUserData[Generic].value need to be volatile to guarantee visibility? – Matt McHenry Oct 21 '10 at 01:34
  • 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 Oct 21 '10 at 03:20
0

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
  • 22,406
  • 7
  • 62
  • 73
  • Doesn't FooWidget.host need to be volatile to guarantee visibility? – Matt McHenry Oct 21 '10 at 01:33
  • 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 Oct 21 '10 at 02:21
-1

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
  • 20,009
  • 8
  • 65
  • 64
  • 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 Oct 20 '10 at 20:59
  • @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 Oct 21 '10 at 01:31
  • You know that assuming everything goes to plan. If the thread gets lost, encounters an exception, etc... who knows? – andersoj Oct 21 '10 at 02:23
-1

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
  • 20,009
  • 8
  • 65
  • 64
  • 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 Oct 20 '10 at 20:58