Zwracanie wartości z Thread


95

Mam metodę z rozszerzeniem HandlerThread. Wartość zostaje zmieniona wewnątrz Threadi chciałbym zwrócić ją do test()metody. Czy jest na to sposób?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}

Jeśli główny wątek musi czekać na zakończenie wątku obsługi przed powrotem z metody, po co w pierwszej kolejności używać wątku obsługi?
JB Nizet,

2
@JBNizet Nie uwzględniłem złożoności tego, co faktycznie robi Wątek. Dostaje współrzędne GPS, więc tak, potrzebuję wątku.
Neeta

2
Niezależnie od złożoności wątku, jeśli wątek uruchamiający go natychmiast czeka na swój wynik po uruchomieniu, nie ma sensu rozpoczynać innego wątku: wątek początkowy zostanie zablokowany tak, jakby sam wykonywał pracę.
JB Nizet,

@JBNizet Nie jestem pewien, co masz na myśli… czy mógłbyś wyjaśnić to w inny sposób?
Neeta

1
Wątek służy do wykonywania czegoś w tle i wykonywania innych czynności podczas wykonywania wątku w tle. Jeśli uruchomisz wątek, a następnie zablokujesz natychmiast, aż wątek się zatrzyma, możesz sam wykonać zadanie wykonane przez wątek i nie zrobi to żadnej różnicy, poza tym, że byłoby znacznie prostsze.
JB Nizet,

Odpowiedzi:


76

Możesz użyć lokalnej końcowej tablicy zmiennych. Zmienna musi być typu innego niż pierwotny, aby można było użyć tablicy. Musisz także zsynchronizować dwa wątki, na przykład za pomocą 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.
}

Możesz również użyć znaków Executori w Callableten sposób:

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
Yyy ... nie. Ten kod jest nieprawidłowy. Dostęp do wartości nie jest poprawnie zsynchronizowany.
G. Blake Meike

6
W rzeczywistości nie potrzebujemy jawnej synchronizacji dostępu do wartości ze względu na gwarancje spójności pamięci CountDownLatch. Tworzenie tablicy wartości następuje przed startem uiThread (reguła kolejności programu), która synchronizuje się z przypisaniem wartości 2 do wartości [0] ( początek wątku ), co ma miejsce przed latch.countDown () (reguła kolejności programu), co dzieje się przed latch .await () (gwarancja z CountDownLatch) co następuje przed odczytem z wartości [0] (reguła kolejności programu).
Adam Zalcman

Wygląda na to, że masz rację co do zatrzasku! ... w takim przypadku synchronizacja metody run jest bezużyteczna.
G. Blake Meike

Słuszna uwaga. Musiałem skopiować i wkleić kod OP. Poprawione.
Adam Zalcman


92

Zwykle zrobiłbyś to coś takiego

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Następnie możesz utworzyć wątek i pobrać wartość (zakładając, że wartość została ustawiona)

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

tl;drwątek nie może zwrócić wartości (przynajmniej nie bez mechanizmu wywołania zwrotnego). Powinieneś odwołać się do wątku jak zwykłej klasy i zapytać o wartość.


2
Czy to naprawdę działa? Rozumiem The method getValue() is undefined for the type Thread.
pmichna

1
@pmichna, dobre wykrywanie. Zmiana z t.getValue()na foo.getValue().
Johan Sjöberg

3
Tak! Dobra robota! „niestabilny” ftw! W przeciwieństwie do zaakceptowanej odpowiedzi, ta jest poprawna!
G. Blake Meike

11
@HamzahMalik Aby upewnić się, że nić się kończy użyj Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Te t.join()bloki, aż gwint jest zakończona.
Daniel,

2
@HariKiran tak, zgadza się, każdy wątek zwraca niezależną wartość.
rootExplorr

29

To, czego szukasz, to prawdopodobnie Callable<V>interfejs zamiast Runnablei pobieranie wartości za pomocą Future<V>obiektu, co pozwala również czekać, aż wartość zostanie obliczona. Możesz to osiągnąć za pomocą ExecutorService, z którego możesz uzyskać 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

A co z tym rozwiązaniem?

Nie używa klasy Thread, ale JEST współbieżna iw pewien sposób robi dokładnie to, o co prosisz

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

Teraz wszystko, co musisz zrobić, to powiedzieć, że value.get()za każdym razem, gdy chcesz pobrać zwróconą wartość, wątek jest uruchamiany w tej samej sekundzie, w której podajesz valuewartość, więc nigdy nie musisz o tym mówić threadName.start().

Co to Futurejest, to obietnica dla programu, obiecujesz programowi, że dostaniesz mu potrzebną wartość w najbliższej przyszłości

Jeśli wywołasz .get()go przed zakończeniem, wątek, który go wywołuje, po prostu zaczeka, aż się skończy


Podany kod podzieliłem na dwie rzeczy: jedna to inicjacja puli, którą robię w klasie Application (mówię o Androidzie), a po drugie korzystam z puli w miejscach, w których jej potrzebuję ... Również w odniesieniu do używania Executors.newFixedThreadPool ( 2) Użyłem Executors.newSingleThreadExecutor () .. ponieważ potrzebowałem tylko jednego zadania uruchomionego na raz dla wywołań serwera ... Twoja odpowiedź jest idealna @electirc coffee thanks
Gaurav Pangam

@Electric Skąd wiesz, kiedy uzyskać wartość? Myślę, że oryginalny plakat chciał w swojej metodzie zwrócić tę wartość. Użycie wywołania .get () spowoduje pobranie wartości, ale tylko wtedy, gdy operacja zostanie zakończona. Nie wiedziałby, dzwoniąc w ciemno .get ()
portfoliobuilder

4

Jeśli chcesz uzyskać wartość z metody wywołującej, powinieneś poczekać na zakończenie wątku, co sprawia, że ​​używanie wątków jest trochę bezcelowe.

Aby bezpośrednio odpowiedzieć na twoje pytanie, wartość może być przechowywana w dowolnym zmiennym obiekcie, zarówno metoda wywołująca, jak i wątek mają odniesienie do. Możesz użyć zewnętrznego this, ale nie będzie to szczególnie przydatne poza trywialnymi przykładami.

Mała uwaga na temat kodu w pytaniu: Rozszerzanie Threadjest zwykle kiepskim stylem. Rzeczywiście, niepotrzebne rozszerzanie klas to zły pomysł. Zauważyłem, że runz jakiegoś powodu metoda jest zsynchronizowana. Ponieważ obiekt w tym przypadku jest tym Thread, możesz ingerować we wszystko, do czego Threadużywa jego blokady (w implementacji referencyjnej ma to coś wspólnego z joinIIRC).


„wtedy należy poczekać na zakończenie nitki, co sprawia, że ​​używanie nici jest trochę bezcelowe” świetna uwaga!
likejudo

4
Zwykle jest to bezcelowe, ale w systemie Android nie można wysłać żądania sieciowego do serwera w głównym wątku (aby aplikacja była responsywna), więc będziesz musiał użyć wątku sieciowego. Istnieją scenariusze, w których potrzebujesz wyniku, zanim aplikacja będzie mogła wznowić.
Udi Idan

3

Od wersji Java 8 mamy CompletableFuture. W twoim przypadku możesz użyć metody, supplyAsyncaby uzyskać wynik po wykonaniu.

Proszę znaleźć jakieś odniesienie tutaj .

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

   completableFuture.get() //gives you the value

1

Korzystanie z Future opisanego w powyższych odpowiedziach spełnia swoje zadanie, ale nieco mniej istotnie, ponieważ f.get () blokuje wątek do momentu uzyskania wyniku, co narusza współbieżność.

Najlepszym rozwiązaniem jest skorzystanie z ListenableFuture firmy Guava. Przykład :

    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

Dzięki niewielkim modyfikacjom kodu możesz to osiągnąć w bardziej ogólny sposób.

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

Rozwiązanie :

  1. Utwórz Handlerwątek w interfejsie użytkownika, który nosi nazwęresponseHandler
  2. Zainicjuj to Handlerz Looperwątku interfejsu użytkownika.
  3. W HandlerThread, napisz wiadomość na ten tematresponseHandler
  4. handleMessgaepokazuje Toastwartość otrzymaną z wiadomości. Ten obiekt Message jest ogólny i można wysyłać różne typy atrybutów.

Dzięki takiemu podejściu możesz wysyłać wiele wartości do wątku interfejsu użytkownika w różnych momentach. Możesz uruchomić (opublikować) wiele Runnableobiektów na tym HandlerThreadi każdy Runnablemoże ustawić wartość w Messageobiekcie, którą może odebrać UI Thread.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.