Thread'den dönen değer


97

A ile bir yöntemim var HandlerThread. İçinde bir değer değiştirilir Threadve bunu test()yönteme geri döndürmek istiyorum . Bunu yapmanın bir yolu var mı?

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

Ana iş parçacığı, yöntemden dönmeden önce işleyici iş parçacığının bitmesini beklemek zorundaysa, neden ilk etapta bir işleyici iş parçacığı kullanılsın?
JB Nizet

2
@JBNizet Thread'in gerçekte ne yaptığının karmaşıklığını dahil etmedim. GPS koordinatlarını alıyor, bu yüzden evet bir İş Parçacığına ihtiyacım var.
Neeta

2
İş parçacığının karmaşıklığına bakılmaksızın, onu bırakan iş parçacığı başlattıktan hemen sonra sonucunu beklerse, farklı bir iş parçacığı başlatmanın bir anlamı yoktur: başlangıç ​​iş parçacığı işi kendisi yapmış gibi engellenecektir.
JB Nizet

@JBNizet Ne demek istediğinizden pek emin değilim .. Bunu farklı bir şekilde açıklamanın bir sakıncası var mı?
Neeta

1
Bir iş parçacığı, arka planda bir şey yürütebilmek ve arka plan iş parçacığı yürütülürken başka bir şey yapabilmek için kullanılır. Bir iş parçacığı başlatır ve ardından iş parçacığı durana kadar hemen bloke ederseniz, iş parçacığı tarafından yapılan görevi kendiniz yapabilirsiniz ve çok daha basit olması dışında herhangi bir fark yaratmaz.
JB Nizet

Yanıtlar:


78

Yerel bir son değişken dizisi kullanabilirsiniz. Değişkenin ilkel olmayan türde olması gerekir, böylece bir dizi kullanabilirsiniz. Ayrıca, iki iş parçacığını, örneğin bir CountDownLatch kullanarak senkronize etmeniz gerekir :

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.
}

Bunun gibi bir Executorve a da kullanabilirsiniz 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();
}

5
Hayır. Bu kod doğru değil. Değere erişim doğru şekilde senkronize edilmemiştir.
G. Blake Meike

6
CountDownLatch'in bellek tutarlılığı garantileri nedeniyle değere erişim için açık senkronizasyona ihtiyacımız yok. Değer dizisi oluşturma, latch.countDown () (program sırası kuralı) 'dan önce gerçekleşen 2'nin [0] değerine atanması ( iş parçacığı başlangıcı ) ile senkronize olan uiThread başlangıcından (program sırası kuralı) önce gerçekleşir. .await () (CountDownLatch tarafından garanti edilir) - [0] değerinden okumadan önce (program sırası kuralı) gerçekleşir.
Adam Zalcman

Görünüşe göre Mandal konusunda haklısın! ... bu durumda, çalıştırma yönteminin senkronizasyonu işe yaramaz.
G. Blake Meike

İyi bir nokta. OP'nin kodundan kopyalayıp yapıştırmış olmalıyım. Düzeltildi.
Adam Zalcman


91

Genellikle böyle bir şey yapardın

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Ardından, iş parçacığı oluşturabilir ve değeri alabilirsiniz (değerin ayarlanmış olması koşuluyla)

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

tl;drbir iş parçacığı bir değer döndüremez (en azından bir geri arama mekanizması olmadan). Sıradan bir sınıf gibi bir iş parçacığına başvurmalı ve değeri sormalısınız.


2
Bu gerçekten çalışıyor mu? Anlıyorum The method getValue() is undefined for the type Thread.
pmichna

1
@pmichna, iyi tespit. Dan değiştirme t.getValue()için foo.getValue().
Johan Sjöberg

3
Evet! Aferin! "uçucu" ftw! Kabul edilen cevabın aksine, bu doğru!
G. Blake Meike

11
@HamzahMalik İpliğin bittiğinden emin olmak için kullanın Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. t.join()Konu dek blok bitti.
Daniel

2
@HariKiran evet doğru, her iş parçacığı bağımsız bir değer döndürüyor.
rootExplorr

29

Muhtemelen aradığınız şey , değerin hesaplanmasını beklemenize izin veren, bir nesnenin Callable<V>yerine Runnablegeçecek arabirim ve değeri geri Future<V>almaktır. Bir ile bunu başarabilirsiniz ExecutorServiceadreslerden alabilirsiniz, hangi 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

Bu çözüme ne dersiniz?

Thread sınıfını kullanmaz, ancak eşzamanlıdır ve bir şekilde tam olarak istediğiniz şeyi yapar

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

Şimdi tek yapmanız gereken, ne value.get()zaman iade edilen değerinizi almanız gerekiyorsa, iş parçacığı valuebir değer verdiğiniz anda başlatılır, böylece söylemek zorunda kalmazsınız.threadName.start() üzerinde hiçbir zaman .

Ne Futurebir sözdür , programa verilen , programa yakın gelecekte ihtiyaç duyduğu değeri alacağına söz verirsin

Tamamlanmadan .get()önce onu çağırırsanız , onu çağıran iş parçacığı, tamamlanana kadar bekleyecektir.


Verilen kodu iki şeye ayırdım: Uygulama sınıfında yaptığım havuz başlatma (android hakkında konuşuyorum) ve ikincisi havuzu ihtiyacım olan yerlerde kullanıyorum ... Ayrıca Executors.newFixedThreadPool ( 2) Executors.newSingleThreadExecutor () kullandım .. çünkü sunucu çağrıları için aynı anda çalışan yalnızca bir görev gerekiyor ... Cevabınız mükemmel @electirc coffee teşekkürler
Gaurav Pangam

@Electric değeri ne zaman alacağınızı nasıl anlarsınız? Orijinal posterin yönteminde bu değeri geri vermek istediğine inanıyorum. .Get () çağrısının kullanılması değeri alır, ancak yalnızca işlem tamamlandığında. Kör .get () çağrısıyla bilemezdi
portfoliobuilder

4

Çağırma yöntemindeki değeri istiyorsanız, iş parçacığının bitmesini beklemelidir, bu da iş parçacıkları kullanmayı biraz anlamsız hale getirir.

Sorunuza doğrudan cevap vermek için, değer herhangi bir değişken nesnede saklanabilir, hem çağıran yöntem hem de iş parçacığı her ikisinin de referansı vardır. Dışını kullanabilirsinthis , ancak bu önemsiz örnekler dışında özellikle yararlı olmayacaktır.

Sorudaki kodla ilgili küçük bir not: Genişletme Threadgenellikle kötü bir tarzdır. Aslında gereksiz yere sınıfları genişletmek kötü bir fikirdir. runYöntemin bir nedenle senkronize edildiğini fark ettim . Şimdi bu durumda nesne olduğu için , kilidini Threadne için Threadkullanırsa ona müdahale edebilirsiniz (referans uygulamasında join, IIRC ile ilgili bir şey ).


"o zaman iş parçacığının bitmesini beklemelidir, bu da iş parçacığı kullanımını biraz anlamsız hale getirir" harika bir nokta!
likejudo

4
Genellikle anlamsızdır, ancak Android'de ana İş Parçacığı üzerindeki bir sunucuya ağ isteğinde bulunamazsınız (uygulamayı yanıt verir durumda tutmak için), bu nedenle bir ağ iş parçacığı kullanmanız gerekir. Uygulamanın devam edebilmesi için sonuca ihtiyaç duyduğunuz senaryolar vardır.
Udi Idan

3

Java 8'den itibaren sahibiz CompletableFuture. Sizin durumunuzda, supplyAsyncyürütmeden sonra sonucu almak için yöntemi kullanabilirsiniz .

Lütfen burada biraz referans bulun .

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

   completableFuture.get() //gives you the value

1

Yukarıdaki yanıtlarda açıklanan Future'ı kullanmak işi yapar, ancak f.get () olarak biraz daha az önemli olan, eşzamanlılığı ihlal eden sonucu alana kadar iş parçacığını engeller.

En iyi çözüm Guava'nın ListenableFuture'sini kullanmaktır. Bir örnek :

    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

Kodunuzda küçük değişiklikler yaparak, bunu daha genel bir şekilde elde edebilirsiniz.

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

Çözüm :

  1. Bir HandlerUI İş Parçacığı oluşturun ;responseHandler
  2. İlklendirin Handlergelen LooperUI Konu.
  3. İçinde HandlerThread, bunun üzerine mesaj gönderresponseHandler
  4. handleMessgaeToastmesajdan alınan değeri gösterir . Bu Mesaj nesnesi geneldir ve farklı türde nitelikler gönderebilirsiniz.

Bu yaklaşımla, farklı zamanlarda UI iş parçacığına birden çok değer gönderebilirsiniz. Bunun Runnableüzerinde birçok nesneyi çalıştırabilir (postalayabilirsiniz) HandlerThreadve her biri nesnede UI Thread tarafından alınabilen bir Runnabledeğer ayarlayabilir Message.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.