Devolviendo valor de Thread

93

Tengo un método con HandlerThread. Se cambia un valor dentro de Thready me gustaría devolverlo al test()método. ¿Hay alguna forma de hacer esto?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}
Neeta
fuente
Si el hilo principal debe esperar a que termine el hilo del controlador antes de regresar del método, ¿por qué usar un hilo del controlador en primer lugar?
JB Nizet
1
@JBNizet No incluí la complejidad de lo que realmente hace Thread. Está obteniendo coordenadas gps, así que sí, necesito un hilo.
Neeta
2
Independientemente de la complejidad del hilo, si el hilo que lo inicia espera inmediatamente su resultado después de iniciarlo, no tiene sentido iniciar un hilo diferente: el hilo de inicio se bloqueará como si hiciera el trabajo él mismo.
JB Nizet
@JBNizet No estoy muy seguro de lo que quieres decir ... ¿te importaría explicarlo de otra manera?
Neeta
1
Un hilo se utiliza para poder ejecutar algo en segundo plano y poder hacer otra cosa mientras se ejecuta el hilo de fondo. Si inicia un hilo y luego lo bloquea inmediatamente hasta que el hilo se detenga, podría hacer usted mismo la tarea realizada por el hilo y no haría ninguna diferencia, excepto que sería mucho más simple.
JB Nizet

Respuestas:

75

Puede utilizar una matriz de variables final local. La variable debe ser de tipo no primitivo, por lo que puede usar una matriz. También necesita sincronizar los dos hilos, por ejemplo, usando un 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.
}

También puede usar un Executory un 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
fuente
3
Mmm no. Este código no es correcto. El acceso al valor no está sincronizado correctamente.
G. Blake Meike
5
En realidad, no necesitamos una sincronización explícita para los accesos al valor debido a las garantías de consistencia de la memoria de CountDownLatch. La creación de la matriz de valores ocurre antes del inicio de uiThread (regla de orden del programa) que se sincroniza con la asignación de 2 al valor [0] ( inicio de subproceso ) que ocurre antes de latch.countDown () (regla de orden de programa) que sucede antes de latch .await () (garantía de CountDownLatch) que sucede antes de la lectura del valor [0] (regla de orden del programa).
Adam Zalcman
¡Parece que tienes razón sobre el Latch! ... en cuyo caso, la sincronización del método de ejecución es inútil.
G. Blake Meike
Buen punto. Debo haber copiado y pegado el código de OP. Corregido.
Adam Zalcman
Aquí hay un ejemplo de CountDownLatch: developer.android.com/reference/java/util/concurrent/…
Seagull
89

Por lo general, lo harías de esta manera

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Luego puede crear el hilo y recuperar el valor (dado que el valor se ha establecido)

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

tl;drun hilo no puede devolver un valor (al menos no sin un mecanismo de devolución de llamada). Debe hacer referencia a un hilo como una clase ordinaria y preguntar por el valor.

Johan Sjöberg
fuente
2
¿Esto realmente funciona? Yo entiendo The method getValue() is undefined for the type Thread.
pmichna
1
@pmichna, buen avistamiento. Cambiar de t.getValue()a foo.getValue().
Johan Sjöberg
3
¡Si! ¡Bien hecho! "volátil" ftw! A diferencia de la respuesta aceptada, ¡esta es correcta!
G. Blake Meike
11
@HamzahMalik Para asegurarse de que el hilo termine de usar Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Los t.join()bloques hasta terminar el hilo.
Daniel
2
@HariKiran sí correcto, cada hilo devuelve un valor independiente.
rootExplorr
28

Lo que está buscando es probablemente la Callable<V>interfaz en lugar de Runnabley recuperar el valor con un Future<V>objeto, lo que también le permite esperar hasta que se haya calculado el valor. Puede lograr esto con un ExecutorService, que puede obtener de 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();
}
Detheroc
fuente
8

¿Qué tal esta solución?

No usa la clase Thread, pero ES concurrente y, de alguna manera, hace exactamente lo que solicita

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

Ahora todo lo que debe hacer es decir value.get()cada vez que necesite tomar su valor devuelto, el hilo se inicia en el mismo segundo en que da valueun valor para que nunca tenga que decirthreadName.start() .

Lo que Futurees, es una promesa al programa, usted le promete al programa que obtendrá el valor que necesita en algún momento en el futuro cercano

Si lo llama .get()antes de que termine, el hilo que lo llama simplemente esperará hasta que esté listo

Café Eléctrico
fuente
Separé el código proporcionado en dos cosas, una es la iniciación del grupo que hago en la clase de aplicación (estoy hablando de Android) y, en segundo lugar, uso el grupo en los lugares donde lo necesito ... También con respecto al uso de Executors.newFixedThreadPool ( 2) Usé Executors.newSingleThreadExecutor () ... ya que solo necesitaba una tarea ejecutándose a la vez para llamadas al servidor ... Su respuesta es perfecta @electirc café gracias
Gaurav Pangam
@Electric, ¿cómo sabe cuándo obtener el valor? Creo que el cartel original quería devolver este valor en su método. El uso de la llamada .get () recuperará el valor, pero solo si se completa la operación. No lo sabría con una llamada ciega .get ()
portfoliobuilder
4

Si desea obtener el valor del método de llamada, debe esperar a que finalice el hilo, lo que hace que el uso de hilos sea un poco inútil.

Para responder directamente a su pregunta, el valor se puede almacenar en cualquier objeto mutable al que tanto el método de llamada como el hilo tienen una referencia. Podrías usar el exteriorthis , pero eso no será particularmente útil más que para ejemplos triviales.

Una pequeña nota sobre el código en la pregunta: la extensión Threadsuele ser un estilo deficiente. De hecho, extender las clases innecesariamente es una mala idea. Noto que su runmétodo está sincronizado por alguna razón. Ahora, como el objeto en este caso es el Thread, puede interferir con lo que sea que Threaduse su bloqueo (en la implementación de referencia, algo que ver con joinIIRC).

Tom Hawtin - tackline
fuente
"entonces debería esperar a que termine el hilo, lo que hace que el uso de hilos sea un poco inútil" ¡gran punto!
likejudo
4
Por lo general, no tiene sentido, pero en Android no puede realizar una solicitud de red a un servidor en el hilo principal (para mantener la aplicación en respuesta), por lo que tendrá que usar un hilo de red. Hay escenarios en los que necesita el resultado antes de que la aplicación pueda reanudarse.
Udi Idan
2

Desde Java 8 en adelante tenemos CompletableFuture. En su caso, puede usar el método supplyAsync para obtener el resultado después de la ejecución.

Encuentre alguna ref. aquí https://www.baeldung.com/java-completablefuture#Processing

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

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

El uso de Future descrito en las respuestas anteriores hace el trabajo, pero un poco menos significativamente ya que f.get () bloquea el hilo hasta que obtiene el resultado, lo que viola la concurrencia.

La mejor solución es utilizar ListenableFuture de Guava. Un ejemplo :

    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
fuente
1

Con pequeñas modificaciones en su código, puede lograrlo de una manera más 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();

Solucion:

  1. Cree un Handlersubproceso en la interfaz de usuario, que se llama comoresponseHandler
  2. Inicialice esto Handlerdesde LooperUI Thread.
  3. En HandlerThread, publicar un mensaje en esteresponseHandler
  4. handleMessgaemuestra un Toastvalor recibido del mensaje. Este objeto de mensaje es genérico y puede enviar diferentes tipos de atributos.

Con este enfoque, puede enviar varios valores al hilo de la interfaz de usuario en diferentes momentos. Puede ejecutar (publicar) muchos Runnableobjetos en esto HandlerThready cada uno Runnablepuede establecer un valor en el Messageobjeto, que puede ser recibido por UI Thread.

Ravindra babu
fuente