Retornando valor do Tópico

93

Eu tenho um método com um HandlerThread. Um valor é alterado dentro do Threade eu gostaria de devolvê-lo ao test()método. Existe uma maneira de fazer isso?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}
Neeta
fonte
Se o thread principal deve esperar que o thread do manipulador termine antes de retornar do método, por que usar um thread do manipulador em primeiro lugar?
JB Nizet
1
@JBNizet Não incluí a complexidade do que o Thread realmente faz. Ele está recebendo coordenadas GPS, então sim, eu preciso de um Thread.
Neeta
2
Independentemente da complexidade do encadeamento, se o encadeamento que o inicia imediatamente espera seu resultado após iniciá-lo, não há motivo para iniciar um encadeamento diferente: o encadeamento inicial será bloqueado como se tivesse feito o próprio trabalho.
JB Nizet
@JBNizet Não tenho muita certeza do que você quer dizer ... você se importaria de explicar de uma maneira diferente?
Neeta
1
Um thread é usado para executar algo em segundo plano e fazer outra coisa enquanto o thread em segundo plano é executado. Se você iniciar um thread e, em seguida, bloquear imediatamente até que ele pare, você mesmo poderia fazer a tarefa realizada pelo thread e não faria nenhuma diferença, exceto que seria muito mais simples.
JB Nizet

Respostas:

75

Você pode usar uma matriz de variável final local. A variável precisa ser do tipo não primitivo, então você pode usar uma matriz. Você também precisa sincronizar os dois threads, por exemplo, usando um 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.
}

Você também pode usar um Executore um Callablecomo este:

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();
}
Adam Zalcman
fonte
3
Hum ... não. Este código não está correto. O acesso ao valor não está sincronizado corretamente.
G. Blake Meike
5
Na verdade, não precisamos de sincronização explícita para os acessos ao valor devido às garantias de consistência de memória de CountDownLatch. A criação da matriz de valor acontece - antes do início do uiThread (regra de ordem do programa) que sincroniza - com a atribuição de 2 ao valor [0] ( início da thread ) que acontece - antes de latch.countDown () (regra de ordem do programa) que acontece - antes do latch .await () (garantia de CountDownLatch) que ocorre antes da leitura do valor [0] (regra de ordem do programa).
Adam Zalcman
Parece que você está certo sobre o Latch! ... nesse caso, a sincronização do método de execução é inútil.
G. Blake Meike
Bom ponto. Devo ter copiado e colado do código do OP. Corrigido.
Adam Zalcman
Aqui está um exemplo de CountDownLatch: developer.android.com/reference/java/util/concurrent/…
Seagull,
87

Normalmente você faria algo assim

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Em seguida, você pode criar o segmento e recuperar o valor (desde que o valor tenha sido definido)

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

tl;drum thread não pode retornar um valor (pelo menos não sem um mecanismo de retorno de chamada). Você deve fazer referência a um thread como uma classe comum e pedir o valor.

Johan Sjöberg
fonte
2
Isso realmente funciona? Eu entendo The method getValue() is undefined for the type Thread.
pmichna
1
@pmichna, boa localização. Mudando de t.getValue()para foo.getValue().
Johan Sjöberg
3
Sim! Bem feito! ftw "volátil"! Ao contrário da resposta aceita, esta está correta!
G. Blake Meike
11
@HamzahMalik Para ter certeza de que a rosca termina, use Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Os t.join()blocos até que o segmento seja concluído.
Daniel
2
@HariKiran sim correto, cada thread retorna um valor independente.
rootExplorr
28

O que você está procurando provavelmente é a Callable<V>interface no lugar de Runnablee recuperando o valor com um Future<V>objeto, o que também permite que você espere até que o valor tenha sido calculado. Você pode conseguir isso com um ExecutorService, do qual você pode obter 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();
}
Deteroc
fonte
8

Que tal essa solução?

Ele não usa a classe Thread, mas é concorrente e, de certa forma, faz exatamente o que você pede

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

Agora, tudo o que você faz é dizer value.get()sempre que precisar obter o valor retornado, o encadeamento é iniciado no exato segundo em que você fornece valueum valor, para que você nunca precise dizer threadName.start()nada sobre ele.

O que Futureé, é uma promessa para o programa, você promete ao programa que obterá o valor de que precisa em um futuro próximo

Se você chamá .get()-lo antes de terminar, o thread que o está chamando simplesmente esperará até que esteja concluído

Café Elétrico
fonte
Separei o código fornecido em duas coisas, uma é a iniciação do pool que faço na classe Application (estou falando sobre o Android) e, em segundo lugar, eu uso o pool nos lugares onde preciso ... Também em relação ao uso de Executors.newFixedThreadPool ( 2) Eu usei Executors.newSingleThreadExecutor () .. como eu precisava de apenas uma tarefa em execução por vez para chamadas do servidor ... Sua resposta é perfeita @electirc café obrigado
Gaurav Pangam
@Electric como você sabe quando obter o valor? Acredito que o autor da postagem original queria retornar esse valor em seu método. O uso da chamada .get () recuperará o valor, mas apenas se a operação for concluída. Ele não saberia com uma chamada cega para .get ()
portfólio
4

Se você quiser o valor do método de chamada, ele deve aguardar o término do thread, o que torna o uso de threads um pouco inútil.

Para responder diretamente a sua pergunta, o valor pode ser armazenado em qualquer objeto mutável, tanto o método de chamada quanto o thread têm uma referência. Você poderia usar o externo this, mas isso não será particularmente útil, exceto para exemplos triviais.

Uma pequena observação sobre o código em questão: a extensão Threadgeralmente é um estilo ruim. Na verdade, estender as aulas desnecessariamente é uma má ideia. Percebi que seu runmétodo está sincronizado por algum motivo. Agora, como o objeto neste caso é o, Threadvocê pode interferir com o que quer que Threaduse seu bloqueio para (na implementação de referência, algo a ver com join, IIRC).

Tom Hawtin - tackline
fonte
"então ele deve esperar o fio terminar, o que torna o uso de fios um pouco inútil" grande ponto!
likejudo
4
Geralmente é inútil, mas no Android você não pode fazer uma solicitação de rede para um servidor no Thread principal (para manter o aplicativo responsivo), então você terá que usar um thread de rede. Existem cenários em que você precisa do resultado antes que o aplicativo possa ser retomado.
Udi Idan
2

Do Java 8 em diante, temos CompletableFuture. No seu caso, você pode usar o método supplyAsync para obter o resultado após a execução.

Por favor, encontre algumas ref. aqui https://www.baeldung.com/java-completablefuture#Processing

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

   completableFuture.get() //gives you the value
Vinto
fonte
1

Usar Future descrito nas respostas acima faz o trabalho, mas um pouco menos significativamente, como f.get (), bloqueia o thread até obter o resultado, o que viola a simultaneidade.

A melhor solução é usar o ListenableFuture da Guava. Um exemplo :

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

        }
    };
bitsabhi
fonte
1

Com pequenas modificações em seu código, você pode conseguir isso de uma forma mais genérica.

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

Solução:

  1. Crie um Handler na UI Thread, que é chamado deresponseHandler
  2. Inicialize isso HandlerdeLooper do UI Thread.
  3. Em HandlerThread, postar mensagem sobre esteresponseHandler
  4. handleMessgaemostra um Toastcom o valor recebido da mensagem. Este objeto Message é genérico e você pode enviar diferentes tipos de atributos.

Com essa abordagem, você pode enviar vários valores para o thread de interface do usuário em momentos diferentes. Você pode executar (postar) muitos Runnableobjetos neste HandlerThreade cada um Runnablepode definir o valor no Messageobjeto, que pode ser recebido pelo UI Thread.

Ravindra babu
fonte