Mengembalikan nilai dari Thread

97

Saya memiliki metode dengan a HandlerThread. Nilai diubah di dalam Threaddan saya ingin mengembalikannya ke test()metode. Apakah ada cara untuk melakukan ini?

public void test()
{   
    Thread uiThread = new HandlerThread("UIHandler"){
        public synchronized void run(){
            int value; 
            value = 2; //To be returned to test()
        }
    };
    uiThread.start();
}
Neeta
sumber
Jika utas utama harus menunggu utas penangan selesai sebelum kembali dari metode, mengapa menggunakan utas penangan di tempat pertama?
JB Nizet
2
@JBNizet Saya tidak menyertakan kerumitan dari apa yang sebenarnya dilakukan Thread. Ini mendapatkan koordinat gps jadi ya saya butuh Thread.
Neeta
2
Terlepas dari kerumitan utas, jika utas yang memulai itu segera menunggu hasilnya setelah memulainya, tidak ada gunanya memulai utas yang berbeda: utas awal akan diblokir seolah-olah itu melakukan pekerjaan itu sendiri.
JB Nizet
@JBNizet Saya tidak terlalu yakin apa yang Anda maksud .. maukah Anda menjelaskannya dengan cara yang berbeda?
Neeta
1
Sebuah utas digunakan untuk dapat mengeksekusi sesuatu di latar belakang, dan dapat melakukan sesuatu yang lain saat utas latar belakang dijalankan. Jika Anda memulai utas, lalu segera memblokir hingga utas berhenti, Anda dapat melakukan tugas yang dilakukan oleh utas itu sendiri, dan itu tidak akan membuat perbedaan apa pun, kecuali itu akan jauh lebih sederhana.
JB Nizet

Jawaban:

77

Anda dapat menggunakan array variabel final lokal. Variabel harus berjenis non-primitif, sehingga Anda dapat menggunakan array. Anda juga perlu menyinkronkan kedua utas, misalnya menggunakan 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.
}

Anda juga dapat menggunakan Executordan Callableseperti ini:

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
sumber
5
Um ... tidak. Kode ini salah. Akses ke nilai tidak disinkronkan dengan benar.
G. Blake Meike
6
Kami sebenarnya tidak memerlukan sinkronisasi eksplisit untuk akses ke nilai karena jaminan konsistensi memori dari CountDownLatch. Pembuatan larik nilai terjadi sebelum uiThread mulai (aturan urutan program) yang disinkronkan dengan penugasan 2 ke nilai [0] ( awal utas ) yang terjadi-sebelum latch.countDown () (aturan urutan program) yang terjadi-sebelum latch .await () (jaminan dari CountDownLatch) yang terjadi-sebelum pembacaan dari nilai [0] (aturan urutan program).
Adam Zalcman
Sepertinya Anda benar tentang Latch! ... dalam hal ini, sinkronisasi metode run tidak berguna.
G. Blake Meike
Poin yang bagus. Saya harus menyalin-tempel dari kode OP. Diperbaiki.
Adam Zalcman
Berikut adalah contoh CountDownLatch: developer.android.com/reference/java/util/concurrent/…
Seagull
92

Biasanya Anda akan melakukannya seperti ini

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Kemudian Anda dapat membuat utas dan mengambil nilainya (mengingat nilainya telah ditetapkan)

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

tl;drutas tidak dapat mengembalikan nilai (setidaknya tidak tanpa mekanisme panggilan balik). Anda harus mereferensikan utas seperti kelas biasa dan menanyakan nilainya.

Johan Sjöberg
sumber
2
Apakah ini benar-benar berhasil? Saya mengerti The method getValue() is undefined for the type Thread.
pmichna
1
@pmichna, bercak bagus. Mengubah dari t.getValue()menjadi foo.getValue().
Johan Sjöberg
3
Iya! Sudah selesai dilakukan dengan baik! "volatile" ftw! Berbeda dengan jawaban yang diterima, yang ini benar!
G. Blake Meike
11
@Hamzah Untuk memastikan utas selesai digunakan Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. The t.join()blok sampai thread selesai.
Daniel
2
@HariKiran ya benar, setiap utas mengembalikan nilai independen.
rootExplorr
29

Apa yang Anda cari mungkin adalah Callable<V>antarmuka yang menggantikan Runnable, dan mengambil nilai dengan Future<V>objek, yang juga memungkinkan Anda menunggu hingga nilai telah dihitung. Anda dapat mencapai ini dengan ExecutorService, yang dapat Anda peroleh dari 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
sumber
8

Bagaimana dengan solusi ini?

Itu tidak menggunakan kelas Thread, tetapi itu IS bersamaan, dan dengan cara itu melakukan apa yang Anda minta

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

Sekarang yang Anda lakukan hanyalah mengatakan value.get()kapan pun Anda perlu mengambil nilai yang dikembalikan, utas dimulai begitu Anda memberikan valuenilai sehingga Anda tidak perlu mengatakannya threadName.start().

Apa Futureitu, adalah janji untuk program, Anda berjanji pada program bahwa Anda akan mendapatkan nilai yang dibutuhkannya dalam waktu dekat

Jika Anda memanggilnya .get()sebelum selesai, utas yang memanggilnya hanya akan menunggu sampai selesai

Kopi Elektrik
sumber
Saya memisahkan kode yang disediakan menjadi dua hal yang pertama adalah inisiasi pool yang saya lakukan di kelas Aplikasi (saya berbicara tentang android) dan kedua saya menggunakan pool di tempat-tempat di mana saya membutuhkannya ... Juga mengenai penggunaan Executors.newFixedThreadPool ( 2) saya menggunakan Executors.newSingleThreadExecutor () .. karena saya hanya memerlukan satu tugas yang berjalan pada satu waktu untuk panggilan server ... Jawaban Anda sempurna @electirc coffee thanks
Gaurav Pangam
@Listrik bagaimana Anda tahu kapan mendapatkan nilai? Saya yakin poster asli ingin mengembalikan nilai ini dalam metodenya. Menggunakan panggilan .get () akan mengambil nilainya, tetapi hanya jika operasi selesai. Dia tidak akan tahu dengan panggilan .get () buta
portofolio
4

Jika Anda menginginkan nilai dari metode pemanggilan, maka itu harus menunggu hingga utas selesai, yang membuat penggunaan utas sedikit tidak berguna.

Untuk menjawab pertanyaan Anda secara langsung, nilai dapat disimpan dalam objek yang bisa berubah apa pun, baik metode pemanggil maupun utas yang memiliki referensi. Anda bisa menggunakan bagian luarnya this, tetapi itu tidak akan berguna selain untuk contoh-contoh yang sepele.

Sedikit catatan tentang kode dalam pertanyaan: Memperluas Threadbiasanya gaya yang buruk. Memang memperpanjang kelas secara tidak perlu adalah ide yang buruk. Saya melihat Anda runmetode disinkronkan karena beberapa alasan. Sekarang sebagai objek dalam kasus ini adalah ThreadAnda dapat mengganggu apa pun yang Threadmenggunakan kunci untuk (dalam implementasi referensi, ada hubungannya dengan join, IIRC).

Tom Hawtin - tackline
sumber
"maka itu harus menunggu utas selesai, yang membuat penggunaan utas menjadi sedikit tidak berguna" poin bagus!
likejudo
4
Biasanya tidak ada gunanya, tetapi di Android Anda tidak dapat membuat permintaan jaringan ke server di Thread utama (agar aplikasi tetap responsif) sehingga Anda harus menggunakan thread jaringan. Ada beberapa skenario di mana Anda memerlukan hasilnya sebelum aplikasi dapat dilanjutkan.
Udi Idan
3

Dari Java 8 dan seterusnya kami punya CompletableFuture. Dalam kasus Anda, Anda dapat menggunakan metode ini supplyAsyncuntuk mendapatkan hasil setelah eksekusi.

Temukan beberapa referensi di sini .

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

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

Menggunakan Future yang dijelaskan dalam jawaban di atas melakukan tugasnya, tetapi sedikit kurang signifikan karena f.get (), memblokir utas hingga mendapatkan hasil, yang melanggar konkurensi.

Solusi terbaik adalah menggunakan ListenableFuture Guava. Sebuah contoh :

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

Dengan sedikit modifikasi pada kode Anda, Anda dapat melakukannya dengan cara yang lebih umum.

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

Solusi:

  1. Buat Handlerdi UI Thread, yang disebut sebagairesponseHandler
  2. Inisialisasi ini Handlerdari LooperUI Thread.
  3. Masuk HandlerThread, posting pesan tentang iniresponseHandler
  4. handleMessgaemenunjukkan Toastnilai yang diterima dari pesan. Objek Pesan ini bersifat umum dan Anda dapat mengirim jenis atribut yang berbeda.

Dengan pendekatan ini, Anda dapat mengirim beberapa nilai ke thread UI pada titik waktu yang berbeda. Anda dapat menjalankan (memposting) banyak Runnableobjek di sini HandlerThreaddan masing-masing Runnabledapat mengatur nilai dalam Messageobjek, yang dapat diterima oleh UI Thread.

Ravindra babu
sumber