คืนค่าจากเธรด


97

ฉันมีวิธีการกับไฟล์HandlerThread. ค่าได้รับการเปลี่ยนแปลงภายในThreadและฉันต้องการส่งคืนกลับเป็นtest()วิธีการ มีวิธีทำไหม?

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

ถ้าเธรดหลักต้องรอให้เธรดตัวจัดการเสร็จสิ้นก่อนที่จะกลับมาจากเมธอดทำไมต้องใช้เธรดตัวจัดการตั้งแต่แรก?
JB Nizet

2
@JBNizet ฉันไม่ได้รวมความซับซ้อนของสิ่งที่เธรดทำจริง มันได้รับพิกัด gps ใช่ฉันต้องการเธรด
Neeta

2
โดยไม่คำนึงถึงความซับซ้อนของเธรดหากเธรดที่ยกย่องเธรดนั้นจะรอผลลัพธ์หลังจากเริ่มต้นทันทีจะไม่มีจุดเริ่มต้นเธรดอื่นเธรดเริ่มต้นจะถูกบล็อกราวกับว่าเธรดทำงานเอง
JB Nizet

@JBNizet ฉันไม่แน่ใจเหมือนกันว่าคุณหมายถึงอะไร
Neeta

1
เธรดถูกใช้เพื่อให้สามารถดำเนินการบางอย่างในพื้นหลังและสามารถทำอย่างอื่นได้ในขณะที่เธรดพื้นหลังทำงาน หากคุณเริ่มเธรดแล้วบล็อกทันทีจนกว่าเธรดจะหยุดคุณสามารถทำงานโดยเธรดได้ด้วยตัวเองและมันจะไม่สร้างความแตกต่างใด ๆ นอกจากจะง่ายกว่ามาก
JB Nizet

คำตอบ:


77

คุณสามารถใช้อาร์เรย์ตัวแปรสุดท้ายแบบโลคัล ตัวแปรต้องเป็นประเภทที่ไม่ใช่พื้นฐานดังนั้นคุณจึงสามารถใช้อาร์เรย์ได้ คุณต้องซิงโครไนซ์สองเธรดตัวอย่างเช่นการใช้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.
}

คุณยังสามารถใช้Executorและสิ่ง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
อืมไม่. รหัสนี้ไม่ถูกต้อง การเข้าถึงค่าไม่ได้รับการซิงโครไนซ์อย่างถูกต้อง
G.Blake Meike

6
เราไม่ต้องการการซิงโครไนซ์อย่างชัดเจนสำหรับการเข้าถึงค่าเนื่องจากการรับประกันความสอดคล้องของหน่วยความจำของ CountDownLatch การสร้างอาร์เรย์ค่าเกิดขึ้นก่อนเริ่ม uiThread (กฎลำดับโปรแกรม) ซึ่งซิงโครไนซ์กับการกำหนดค่า 2 เป็นค่า [0] ( เธรดเริ่มต้น ) ซึ่งเกิดขึ้นก่อน latch.countDown () (กฎลำดับโปรแกรม) ซึ่งเกิดขึ้นก่อนสลัก .await () (รับประกันจาก CountDownLatch) ซึ่งเกิดขึ้นก่อนการอ่านจากค่า [0] (กฎลำดับโปรแกรม)
Adam Zalcman

ดูเหมือนว่าคุณจะพูดถูกเกี่ยวกับ Latch! ... ในกรณีนี้การซิงโครไนซ์ของวิธีการเรียกใช้จะไร้ประโยชน์
G.Blake Meike

จุดดี. ฉันต้องคัดลอกจากรหัสของ OP แก้ไขแล้ว.
Adam Zalcman

นี่คือตัวอย่างของ CountDownLatch: developer.android.com/reference/java/util/concurrent/…
Seagull

92

โดยปกติคุณจะทำสิ่งนี้

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

จากนั้นคุณสามารถสร้างเธรดและดึงค่า (เนื่องจากได้ตั้งค่าไว้)

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

tl;drเธรดไม่สามารถส่งคืนค่าได้ (อย่างน้อยก็ไม่มีกลไกการโทรกลับ) คุณควรอ้างอิงเธรดเหมือนคลาสธรรมดาและถามค่า


2
ใช้งานได้จริงหรือ? ฉันเข้าใจThe method getValue() is undefined for the type Threadแล้ว
pmichna

1
@pmichna การจำที่ดี เปลี่ยนจากt.getValue()เป็นfoo.getValue().
Johan Sjöberg

3
ใช่ ทำได้ดี! "ระเหย" ftw! ไม่เหมือนกับคำตอบที่ยอมรับข้อนี้ถูกต้อง!
G.Blake Meike

11
@HamzahMalik Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();เพื่อให้แน่ใจว่าเสร็จสิ้นการใช้ด้าย t.join()บล็อกจนด้ายเสร็จสิ้น
Daniel

2
@HariKiran ใช่ถูกต้องแต่ละเธรดส่งคืนค่าอิสระ
rootExplorr

29

สิ่งที่คุณกำลังมองหาน่าจะเป็นCallable<V>อินเทอร์เฟซแทนRunnableและการดึงค่าด้วยFuture<V>อ็อบเจกต์ซึ่งช่วยให้คุณรอจนกว่าค่าจะได้รับการคำนวณ คุณสามารถบรรลุนี้กับที่คุณจะได้รับจากExecutorServiceExecutors.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

วิธีแก้ปัญหานี้เป็นอย่างไร?

มันไม่ได้ใช้คลาสเธรด แต่มันทำงานพร้อมกันและในลักษณะที่ทำตามที่คุณร้องขอ

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

ตอนนี้สิ่งที่คุณทำคือพูดvalue.get()เมื่อใดก็ตามที่คุณต้องการคว้ามูลค่าที่ส่งคืนเธรดจะเริ่มต้นในวินาทีที่คุณให้valueคุณค่าดังนั้นคุณไม่จำเป็นต้องพูดอะไรthreadName.start()เลย

อะไรFutureคือสัญญากับโปรแกรมคุณสัญญากับโปรแกรมว่าคุณจะได้รับคุณค่าที่ต้องการในอนาคตอันใกล้นี้

หากคุณโทรหา.get()ก่อนที่จะเสร็จเธรดที่เรียกมันจะรอจนกว่าจะเสร็จ


ฉันแยกรหัสที่ให้ไว้เป็นสองสิ่งหนึ่งคือการเริ่มต้นพูลที่ฉันทำในคลาสแอปพลิเคชัน (ฉันกำลังพูดถึง Android) และประการที่สองฉันใช้พูลในที่ที่ฉันต้องการ ... นอกจากนี้เกี่ยวกับการใช้ Executors.newFixedThreadPool ( 2) ฉันใช้ Executors.newSingleThreadExecutor () .. เพราะฉันต้องการเพียงงานเดียวที่ทำงานพร้อมกันสำหรับการเรียกเซิร์ฟเวอร์ ... คำตอบของคุณสมบูรณ์แบบ @electirc coffee ขอบคุณ
Gaurav Pangam

@ ไฟฟ้าคุณรู้ได้อย่างไรว่าเมื่อไหร่จะได้รับค่า? ฉันเชื่อว่าผู้โพสต์ต้นฉบับต้องการคืนค่านี้ด้วยวิธีการของเขา การใช้การเรียก. get () จะดึงค่า แต่เมื่อการดำเนินการเสร็จสมบูรณ์เท่านั้น เขาจะไม่รู้ด้วยการโทร. get () คนตาบอด
portfoliobuilder

4

หากคุณต้องการค่าจากวิธีการโทรก็ควรรอให้เธรดเสร็จสิ้นซึ่งทำให้การใช้เธรดไม่มีจุดหมาย

เพื่อตอบคำถามของคุณโดยตรงค่านี้สามารถเก็บไว้ในอ็อบเจ็กต์ที่ไม่แน่นอนใด ๆ ทั้งวิธีการโทรและเธรดมีการอ้างอิงถึง คุณสามารถใช้ด้านนอกthisได้ แต่นั่นจะไม่เป็นประโยชน์อย่างยิ่งนอกเหนือจากตัวอย่างเล็กน้อย

หมายเหตุเล็กน้อยเกี่ยวกับรหัสในคำถาม: การขยายThreadมักจะเป็นรูปแบบที่ไม่ดี การขยายชั้นเรียนโดยไม่จำเป็นเป็นความคิดที่ไม่ดี ฉันสังเกตเห็นว่าrunวิธีการของคุณตรงกันด้วยเหตุผลบางประการ ในขณะที่วัตถุในกรณีนี้คือThreadคุณอาจเข้าไปยุ่งเกี่ยวกับสิ่งที่Threadใช้ล็อคสำหรับ (ในการนำไปใช้อ้างอิงบางสิ่งที่เกี่ยวข้องกับjoinIIRC)


"ถ้าอย่างนั้นก็ควรรอให้เธรดเสร็จสิ้นซึ่งทำให้การใช้เธรดเป็นเรื่องที่ไม่มีจุดหมาย" เยี่ยมมาก!
likejudo

4
โดยปกติจะไม่มีจุดหมาย แต่ใน Android คุณไม่สามารถส่งคำขอเครือข่ายไปยังเซิร์ฟเวอร์บนเธรดหลักได้ (เพื่อให้แอปตอบสนองต่อ) ดังนั้นคุณจะต้องใช้เธรดเครือข่าย มีสถานการณ์ที่คุณต้องการผลลัพธ์ก่อนที่แอปจะกลับมาทำงานต่อได้
Udi Idan

3

ตั้งแต่ Java 8 เป็นต้นไปเรามีCompletableFuture. ในกรณีของคุณคุณอาจใช้วิธีการsupplyAsyncเพื่อให้ได้ผลลัพธ์หลังการดำเนินการ

กรุณาหาอ้างอิงบางอย่างที่นี่

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

   completableFuture.get() //gives you the value

1

การใช้ Future ที่อธิบายไว้ในคำตอบข้างต้นจะได้ผล แต่ f.get () จะบล็อกเธรดจนกว่าจะได้รับผลลัพธ์ซึ่งเป็นการละเมิดการทำงานพร้อมกัน

ทางออกที่ดีที่สุดคือใช้ ListenableFuture ของฝรั่ง ตัวอย่าง :

    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

ด้วยการปรับเปลี่ยนโค้ดเล็กน้อยคุณสามารถทำได้โดยทั่วไปมากขึ้น

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

วิธีการแก้ :

  1. สร้างHandlerเธรดใน UI ซึ่งเรียกว่าเป็นresponseHandler
  2. เริ่มต้นสิ่งนี้HandlerจากLooperเธรด UI
  3. ในHandlerThreadโพสต์ข้อความนี้responseHandler
  4. handleMessgaeแสดงToastค่าที่ได้รับจากข้อความ วัตถุข้อความนี้เป็นแบบทั่วไปและคุณสามารถส่งแอตทริบิวต์ประเภทต่างๆได้

ด้วยวิธีนี้คุณสามารถส่งหลายค่าไปยังเธรด UI ในช่วงเวลาที่ต่างกัน คุณสามารถเรียกใช้ (โพสต์) Runnableอ็อบเจ็กต์จำนวนมากได้HandlerThreadและแต่ละอ็อบเจ็กต์Runnableสามารถกำหนดค่าในMessageอ็อบเจ็กต์ซึ่ง UI Thread สามารถรับได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.