Возвращаемое значение из потока

97

У меня есть метод с HandlerThread. Значение изменяется внутри, Threadи я хочу вернуть его test()методу. Есть ли способ сделать это?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}
Neeta
источник
Если основной поток должен дождаться завершения потока обработчика перед возвратом из метода, зачем вообще использовать поток обработчика?
JB Nizet
2
@JBNizet Я не включил сложность того, что на самом деле делает Thread. Он получает координаты GPS, так что да, мне нужен поток.
Neeta 05
2
Независимо от сложности потока, если поток, который запускает его, сразу же ожидает своего результата после его запуска, нет смысла запускать другой поток: начальный поток будет заблокирован, как если бы он сам выполнял работу.
JB Nizet
@JBNizet Я не совсем понимаю, что вы имеете в виду ... не могли бы вы объяснить это по-другому?
Neeta 05
1
Поток используется, чтобы иметь возможность выполнять что-то в фоновом режиме и иметь возможность делать что-то еще, пока выполняется фоновый поток. Если вы запустите поток, а затем немедленно заблокируете его, пока поток не остановится, вы можете выполнить задачу, выполняемую потоком самостоятельно, и это не будет иметь никакого значения, за исключением того, что было бы намного проще.
JB Nizet

Ответы:

77

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

public void test()
{   
    final CountDownLatch latch = new CountDownLatch(1);
    final int[] value = new int[1];
    Thread uiThread = new HandlerThread("UIHandler"){
        @Override
        public void run(){
            value[0] = 2;
            latch.countDown(); // Release await() in the test thread.
        }
    };
    uiThread.start();
    latch.await(); // Wait for countDown() in the UI thread. Or could uiThread.join();
    // value[0] holds 2 at this point.
}

Вы также можете использовать Executorи Callableподобное:

public void test() throws InterruptedException, ExecutionException
{   
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() {
            return 2;
        }
    };
    Future<Integer> future = executor.submit(callable);
    // future.get() returns 2 or raises an exception if the thread dies, so safer
    executor.shutdown();
}
Адам Зальцман
источник
5
Гм ... нет. Этот код неверен. Доступ к значению неправильно синхронизирован.
G. Blake Meike
6
На самом деле нам не нужна явная синхронизация для доступа к значению из-за гарантий согласованности памяти CountDownLatch. Создание массива значений происходит перед запуском uiThread (правило порядка программы), которое синхронизируется, с присвоением 2 значению [0] ( запуск потока ), которое происходит перед latch.countDown () (правило порядка программы), которое происходит перед защелкой .await () (гарантия от CountDownLatch), который происходит до чтения из значения [0] (правило порядка программы).
Адам Зальцман,
Похоже, вы правы насчет защелки! ... в этом случае синхронизация метода запуска бесполезна.
Дж. Блейк Мейке
Хорошая точка зрения. Я, должно быть, скопировал код OP. Исправленный.
Adam Zalcman
Вот пример CountDownLatch: developer.android.com/reference/java/util/concurrent/…
Чайка,
92

Обычно вы делаете что-то вроде этого

 public class Foo implements Runnable {
     private volatile int value;

     @Override
     public void run() {
        value = 2;
     }

     public int getValue() {
         return value;
     }
 }

Затем вы можете создать поток и получить значение (при условии, что значение было установлено)

Foo foo = new Foo();
Thread thread = new Thread(foo);
thread.start();
thread.join();
int value = foo.getValue();

tl;drпоток не может вернуть значение (по крайней мере, без механизма обратного вызова). Вы должны ссылаться на поток, как на обычный класс, и запрашивать значение.

Йохан Сьёберг
источник
2
Это действительно работает? Я понимаю The method getValue() is undefined for the type Thread.
pmichna
1
@pmichna, хорошие пятнышки. Переход с t.getValue()на foo.getValue().
Johan Sjöberg
3
Да! Отлично сработано! "летучий" ftw! В отличие от принятого ответа, этот правильный!
G. Blake Meike
11
@HamzahMalik Чтобы убедиться, что нить закончила использовать Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Эти t.join()блоки до резьбы закончена.
Даниэль
2
@HariKiran да, правильно, каждый поток возвращает независимое значение.
rootExplorr
29

То, что вы ищете, вероятно, - это Callable<V>интерфейс вместо Runnableи получение значения с помощью Future<V>объекта, что также позволяет вам подождать, пока значение не будет вычислено. Этого можно добиться с помощью объекта ExecutorService, из которого можно получить Executors.newSingleThreadExecutor().

public void test() {
    int x;
    ExecutorService es = Executors.newSingleThreadExecutor();
    Future<Integer> result = es.submit(new Callable<Integer>() {
        public Integer call() throws Exception {
            // the other thread
            return 2;
        }
    });
    try {
        x = result.get();
    } catch (Exception e) {
        // failed
    }
    es.shutdown();
}
Детерок
источник
8

Как насчет этого решения?

Он не использует класс Thread, но он является параллельным и в некотором смысле делает именно то, что вы запрашиваете.

ExecutorService pool = Executors.newFixedThreadPool(2); // creates a pool of threads for the Future to draw from

Future<Integer> value = pool.submit(new Callable<Integer>() {
    @Override
    public Integer call() {return 2;}
});

Теперь все, что вы делаете, это говорите value.get()всякий раз, когда вам нужно получить возвращаемое значение, поток запускается в ту самую секунду, когда вы задаете valueзначение, поэтому вам никогда не придется об этом говорить threadName.start().

Что Futureесть, это обещание программе, вы обещаете программе, что вы получите то значение, которое ей нужно, когда-нибудь в ближайшем будущем.

Если вы вызовете .get()его до того, как это будет сделано, вызывающий его поток просто подождет, пока он не будет выполнен.

Электрический кофе
источник
Я разделил предоставленный код на две вещи: одна - это инициация пула, которую я делаю в классе Application (я говорю об Android), а во-вторых, я использую пул в тех местах, где он мне нужен ... Также относительно использования Executors.newFixedThreadPool ( 2) я использовал Executors.newSingleThreadExecutor () .. так как мне нужно было только одно задание, работающее одновременно для вызовов сервера ... Ваш ответ идеален @electirc coffee спасибо
Gaurav Pangam
@Electric, как узнать, когда получить значение? Я считаю, что исходный постер хотел вернуть это значение в своем методе. Использование вызова .get () будет извлекать значение, но только если операция завершена. Он не будет знать , с слепой .get () вызова
portfoliobuilder
4

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

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

Небольшое замечание по коду в вопросе: расширение Threadобычно плохой стиль. Действительно, без необходимости расширять классы - плохая идея. Я заметил, что ваш runметод по какой-то причине синхронизирован. Теперь, поскольку объект в этом случае является объектом, Threadвы можете вмешиваться во все, что Threadиспользует его блокировку (в эталонной реализации, что-то связанное с joinIIRC).

Том Хотин - tackline
источник
"тогда он должен дождаться завершения потока, что делает использование потоков немного бессмысленным" отличный момент!
likejudo
4
Обычно это бессмысленно, но в Android вы не можете сделать сетевой запрос к серверу в основном потоке (чтобы приложение оставалось отзывчивым), поэтому вам придется использовать сетевой поток. Есть сценарии, в которых вам нужен результат до возобновления работы приложения.
Уди Идан
3

Начиная с Java 8 и далее у нас есть CompletableFuture. В вашем случае вы можете использовать метод supplyAsyncдля получения результата после выполнения.

Пожалуйста, найдите здесь ссылку .

    CompletableFuture<Integer> completableFuture
      = CompletableFuture.supplyAsync(() -> yourMethod());

   completableFuture.get() //gives you the value
Винто
источник
1

Использование Future, описанное в ответах выше, выполняет свою работу, но немного менее существенно, чем f.get (), блокирует поток до тех пор, пока он не получит результат, что нарушает параллелизм.

Лучшее решение - использовать ListenableFuture от Guava. Пример :

    ListenableFuture<Void> future = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1, new NamedThreadFactory).submit(new Callable<Void>()
    {
        @Override
        public Void call() throws Exception
        {
            someBackgroundTask();
        }
    });
    Futures.addCallback(future, new FutureCallback<Long>()
    {
        @Override
        public void onSuccess(Long result)
        {
            doSomething();
        }

        @Override
        public void onFailure(Throwable t)
        {

        }
    };
битсабхи
источник
1

С небольшими изменениями в вашем коде вы можете добиться этого более общим способом.

 final Handler responseHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                //txtView.setText((String) msg.obj);
                Toast.makeText(MainActivity.this,
                        "Result from UIHandlerThread:"+(int)msg.obj,
                        Toast.LENGTH_LONG)
                        .show();
            }
        };

        HandlerThread handlerThread = new HandlerThread("UIHandlerThread"){
            public void run(){
                Integer a = 2;
                Message msg = new Message();
                msg.obj = a;
                responseHandler.sendMessage(msg);
                System.out.println(a);
            }
        };
        handlerThread.start();

Решение :

  1. Создайте Handlerпоток пользовательского интерфейса, который называетсяresponseHandler
  2. Инициализируйте это Handlerиз Looperпотока пользовательского интерфейса.
  3. В HandlerThread, опубликуйте сообщение об этомresponseHandler
  4. handleMessgaeпоказывает Toastзначение, полученное из сообщения. Этот объект сообщения является универсальным, и вы можете отправлять различные типы атрибутов.

При таком подходе вы можете отправлять несколько значений потоку пользовательского интерфейса в разное время. Вы можете запускать (публиковать) много Runnableобъектов на этом, HandlerThreadи каждый Runnableможет устанавливать значение в Messageобъекте, которое может быть получено потоком пользовательского интерфейса.

Равиндра бабу
источник