Возвращаемое значение из Thread

У меня есть метод с 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();
}

Ответ 1

Вы можете использовать локальный конечный переменный массив. Переменная должна быть не примитивного типа, поэтому вы можете использовать массив. Вам также необходимо синхронизировать два потока, например, используя 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();
}

Ответ 2

Обычно вы делаете это примерно так

 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 поток не может вернуть значение (по крайней мере, без механизма обратного вызова). Вы должны ссылаться на поток как обычный класс и запрашивать значение.

Ответ 3

То, что вы ищете, возможно, является интерфейсом 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();
}

Ответ 4

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

Он не использует класс 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() на нем до его завершения, поток, вызывающий его, просто будет ждать, пока он не будет выполнен

Ответ 5

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

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

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

Ответ 6

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

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

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

        }
    };

Ответ 8

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

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

Решение:

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

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