Как получить значение, которое должно быть вычислено в другом потоке - PullRequest
3 голосов
/ 21 октября 2010

Во многих случаях поток A требует значение, которое должно быть вычислено в потоке B. (Чаще всего B == EDT.) Рассмотрим этот пример:

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

Конечно, это не компилируется, потому что анонимному внутреннему классу не разрешено писать в host. Какой самый простой, самый чистый способ заставить это работать? Я включил способы, которые я знаю ниже.

Ответы [ 7 ]

2 голосов
/ 21 октября 2010
нет

Нет

Используйте Future<T> и, возможно, Callable<T> и ExecutorService. Future в основном является точным программным выражением того, что вы хотите: обещание будущего ответа и возможность блокировки до получения ответа . Будущее также автоматически оборачивает и представляет вам целый сложный фарраго потенциальных кошмаров и сложностей параллелизма в несколько четко определенных исключений. Это - хорошая вещь , потому что она заставляет вас иметь дело с ними, когда решение по собственной инициативе, вероятно, никогда не раскроет их, за исключением какого-то ненормального, трудно диагностируемого поведения.

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

Для вашего конкретного случая вы, вероятно, можете использовать SwingWorker , который реализует Future. Вместо дубликата, пожалуйста, посмотрите на этот ТАК * вопрос .

1 голос
/ 21 октября 2010
    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));
0 голосов
/ 21 октября 2010

Обратите внимание: автору не нравится этот ответ, это просто ответ "на носу" на конкретный вопрос.

Если вы ищете только ожиданиечтобы минимально изменить вышеприведенный код, чтобы он работал, вы имеете дело с проблемой «внутренний класс может только ссылаться на финал».Выделите именованный внутренний класс вместо анонимного и создайте поле хоста String в этом классе.Передайте этот экземпляр invokeAndWait().Но это все еще странно, по моему скромному мнению, и намного уступает подходу Future<>, который я цитировал выше.

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());
0 голосов
/ 21 октября 2010

Я рекомендую создать класс для этого, примеры ниже:

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

Я расширю этот подход, сделав класс абстрактным и используя обобщенные значения, чтобы можно было возвращать значения любого типа.

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

РЕДАКТИРОВАТЬ: Проверяет, работает ли из EventDispatchThread.

if (SwingUtilities.isEventDispatchThread()) {
    host = doHostnameGen.process() ;
} else {
    SwingUtilities.invokeAndWait(doHostnameGen) ;
    host = doHostnameGen.getValue() ;
}
0 голосов
/ 21 октября 2010

Hack: использовать массив

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?
0 голосов
/ 21 октября 2010

Подробно: использовать внутренний класс

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);
0 голосов
/ 21 октября 2010

Элегантный? Используйте атомарную переменную

final AtomicReference<String> host = new AtomicReference<String>();
SwingUtilities.invokeAndWait(new Runnable() {
    public void run() {
        host.set(JOptionPane.showInputDialog("Enter host name: "));
    }
});
openConnection(host.get());
...