Повернення значення з Thread


93

У мене є метод з 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();
}

Якщо основний потік повинен дочекатися закінчення обробника, перш ніж повертатися з методу, навіщо спочатку використовувати потік обробника?
JB Nizet

1
@JBNizet Я не включав складність того, що насправді робить Thread. Він отримує координати GPS, так що так, мені потрібна нитка.
Neeta

2
Незалежно від складності нитки, якщо нитка, яка її відстає, одразу ж чекає свого результату після її запуску, немає сенсу починати іншу нитку: початкова нитка буде заблокована так, ніби вона сама виконала роботу.
JB Nizet

@JBNizet Я не надто впевнений, що ви маєте на увазі .. чи не могли б ви пояснити це по-іншому?
Neeta

1
Потік використовується для того, щоб виконувати щось у фоновому режимі, а також робити щось інше, поки виконується фоновий потік. Якщо запустити потік, а потім негайно заблокувати, поки потік не зупиниться, ви зможете виконати завдання, виконане потоком, самостійно, і це не зробить ніякої різниці, крім як було б набагато простіше.
JB Nizet

Відповіді:


75

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

3
Гм ... ні. Цей код неправильний. Доступ до значення синхронізовано неправильно.
G. Blake Meike

5
Насправді нам не потрібна явна синхронізація для значення доступів через гарантії узгодженості пам’яті CountDownLatch. Створення масиву значень відбувається - до запуску uiThread (правило замовлення програми), яке синхронізується - із присвоєнням 2 значенню [0] ( запуск потоку ), що відбувається - до latch.countDown () (правило порядку програми), яке відбувається - перед фіксацією .await () (гарантія від CountDownLatch), що відбувається - перед зчитуванням зі значення [0] (правило порядку програми).
Адам Залцман,

Схоже, ви маєте рацію щодо засувки! ... у такому випадку синхронізація методу запуску марна.
G. Blake Meike

Гарна думка. Я мав скопіювати з коду OP. Виправлено.
Адам Залцман,


89

Зазвичай ви робите це приблизно так

 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().
Йоган Шеберг

3
Так! Молодці! "нестабільна" ftw! На відміну від прийнятої відповіді, ця правильна!
G. Blake Meike

11
@HamzahMalik Щоб переконатися, що потік закінчується, використовуйте Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Ці t.join()блоки до різьблення закінчена.
Даніель

2
@HariKiran так, кожен потік повертає незалежне значення.
rootExplorr

28

Те, що ви шукаєте, - це, мабуть, 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 ()
портфоліо

4

Якщо ви хочете значення методу виклику, тоді йому слід почекати закінчення потоку, що робить використання потоків трохи безглуздим.

Щоб безпосередньо відповісти на ваше запитання, значення може зберігатися в будь-якому змінній об'єкті, як метод виклику, так і потік мають посилання. Ви можете використовувати зовнішній this, але це не буде особливо корисним, крім як для тривіальних прикладів.

Маленька примітка щодо коду у питанні: Розширення, Threadяк правило, поганий стиль. Насправді розширення класів без потреби - погана ідея. Я помічаю, що ваш runметод з якихось причин синхронізований. Тепер, оскільки об’єктом у цьому випадку є Threadви, ви можете втручатися в те, що Threadвикористовує його блокування (у еталонній реалізації, щось joinспільне з , IIRC).


"тоді йому слід почекати закінчення потоку, що робить використання потоків трохи безглуздим" чудовим моментом!
likejudo

4
Зазвичай це безглуздо, але в Android ви не можете зробити мережевий запит на сервер в основній темі (щоб підтримка програми реагувала), тому вам доведеться використовувати мережевий потік. Є сценарії, в яких вам потрібен результат, перш ніж програма зможе відновити роботу.
Уді Ідан,

2

Починаючи з Java 8, ми маємо CompletableFuture. У вашому випадку ви можете використовувати метод supplyAsync для отримання результату після виконання.

Будь ласка, знайдіть посилання тут https://www.baeldung.com/java-completablefuture#Processing

    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 з LooperUI Thread.
  3. В HandlerThread , розмістити повідомлення про цеresponseHandler
  4. handleMessgae показує a Toast значення зі значенням, отриманим із повідомлення. Цей об’єкт Повідомлення є загальним, і Ви можете надсилати різні типи атрибутів.

За допомогою цього підходу ви можете надсилати кілька значень до потоку інтерфейсу користувача в різний момент часу. Ви можете запустити (опублікувати) багато Runnableоб’єктів на цьому, HandlerThreadі кожен Runnableможе встановити значення в Messageоб’єкті, яке може отримати UI Thread.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.