Trả lại giá trị từ Chủ đề


93

Tôi có một phương pháp với a HandlerThread. Một giá trị được thay đổi bên trong Threadvà tôi muốn trả nó về test()phương thức. Có cách nào để làm việc này không?

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

Nếu luồng chính phải đợi luồng xử lý kết thúc trước khi quay trở lại từ phương thức, tại sao lại sử dụng luồng xử lý ngay từ đầu?
JB Nizet

1
@JBNizet Tôi không bao gồm sự phức tạp của những gì Chủ đề thực sự làm. Nó nhận được tọa độ gps nên có, tôi cần một Chủ đề.
Neeta

2
Bất kể độ phức tạp của luồng, nếu luồng khởi động nó ngay lập tức đợi kết quả của nó sau khi bắt đầu, thì sẽ không có ích gì khi bắt đầu một luồng khác: luồng bắt đầu sẽ bị chặn như thể nó tự hoạt động.
JB Nizet

@JBNizet Tôi không chắc ý bạn lắm .. bạn có phiền giải thích theo cách khác không?
Neeta

1
Một luồng được sử dụng để có thể thực thi một cái gì đó ở chế độ nền và có thể làm một cái gì đó khác trong khi luồng nền thực thi. Nếu bạn bắt đầu một luồng và sau đó chặn ngay lập tức cho đến khi luồng dừng lại, bạn có thể tự mình thực hiện nhiệm vụ do luồng đó thực hiện và nó sẽ không tạo ra bất kỳ sự khác biệt nào, ngoại trừ nó sẽ đơn giản hơn nhiều.
JB Nizet

Câu trả lời:


75

Bạn có thể sử dụng một mảng biến cuối cùng cục bộ. Biến cần phải có kiểu không nguyên thủy, vì vậy bạn có thể sử dụng một mảng. Bạn cũng cần đồng bộ hóa hai luồng, ví dụ như sử dụng 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.
}

Bạn cũng có thể sử dụng một Executor và một Callablenhư thế này:

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

3
À, không. Mã này không đúng. Quyền truy cập vào giá trị không được đồng bộ hóa chính xác.
G. Blake Meike

5
Chúng tôi thực sự không cần đồng bộ hóa rõ ràng cho các truy cập vào giá trị do đảm bảo tính nhất quán bộ nhớ của CountDownLatch. Việc tạo mảng giá trị xảy ra trước khi bắt đầu uiThread (quy tắc thứ tự chương trình) đồng bộ hóa - với việc gán 2 cho giá trị [0] ( bắt đầu chuỗi ) xảy ra trước latch.countDown () (quy tắc thứ tự chương trình) xảy ra trước khi chốt .await () (đảm bảo từ CountDownLatch) xảy ra trước khi đọc từ giá trị [0] (quy tắc thứ tự chương trình).
Adam Zalcman

Có vẻ như bạn nói đúng về Latch! ... trong trường hợp đó, việc đồng bộ hóa phương thức chạy là vô ích.
G. Blake Meike

Điểm tốt. Tôi phải đã sao chép từ mã của OP. Đã sửa.
Adam Zalcman

Dưới đây là một ví dụ về CountDownLatch: developer.android.com/reference/java/util/concurrent/...
Seagull

89

Thường thì bạn sẽ làm như thế này

 public class Foo implements Runnable {
     private volatile int value;

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

     public int getValue() {
         return value;
     }
 }

Sau đó, bạn có thể tạo luồng và truy xuất giá trị (cho rằng giá trị đã được đặt)

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

tl;drmột luồng không thể trả về một giá trị (ít nhất là không có cơ chế gọi lại). Bạn nên tham chiếu một luồng như một lớp bình thường và yêu cầu giá trị.


2
Điều này có thực sự hiệu quả? Tôi hiểu The method getValue() is undefined for the type Thread.
pmichna

1
@pmichna, phát hiện tốt. Đang thay đổi từ t.getValue()sang foo.getValue().
Johan Sjöberg

3
Đúng! Làm tốt! "dễ bay hơi" ftw! Không giống như câu trả lời được chấp nhận, câu trả lời này đúng!
G. Blake Meike

11
@HamzahMalik Để đảm bảo chuỗi sử dụng xong Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();. Các t.join()khối cho đến khi kết thúc luồng.
Daniel

2
@HariKiran đúng, mỗi luồng trả về một giá trị độc lập.
rootExplorr

28

Những gì bạn đang tìm kiếm có lẽ là Callable<V>giao diện thay thế Runnablevà truy xuất giá trị bằng một Future<V>đối tượng, điều này cũng cho phép bạn đợi cho đến khi giá trị được tính toán. Bạn có thể đạt được điều này với một ExecutorService, mà bạn có thể nhận được từ đó 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

Làm thế nào về giải pháp này?

Nó không sử dụng lớp Thread, nhưng nó đồng thời và theo một cách nào đó nó thực hiện chính xác những gì bạn yêu cầu

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

Bây giờ tất cả những gì bạn làm là nói value.get()bất cứ khi nào bạn cần lấy giá trị trả về của mình, chuỗi được bắt đầu ngay giây phút bạn đưa ra valuegiá trị nên bạn không bao giờ phải nóithreadName.start() về nó.

A Futurelà gì , là một lời hứa đối với chương trình, bạn hứa chương trình mà bạn sẽ nhận được nó giá trị nó cần lúc nào đó trong tương lai gần

Nếu bạn gọi .get()nó trước khi hoàn tất, chuỗi đang gọi nó sẽ chỉ đợi cho đến khi nó hoàn tất


Tôi đã tách mã được cung cấp thành hai thứ một là khởi tạo nhóm mà tôi thực hiện trong lớp Ứng dụng (tôi đang nói về android) và thứ hai tôi sử dụng nhóm ở những nơi tôi cần nó ... Cũng liên quan đến việc sử dụng Executor.newFixedThreadPool ( 2) i sử dụng Executors.newSingleThreadExecutor () .. như tôi cần chỉ có một nhiệm vụ chạy tại một thời điểm cho các cuộc gọi máy chủ ... trả lời của bạn là hoàn hảo @electirc cà phê nhờ
Gaurav Pangam

@Electric làm thế nào để bạn biết khi nào nhận được giá trị? Tôi tin rằng người đăng ban đầu muốn trả lại giá trị này trong phương pháp của mình. Sử dụng lệnh gọi .get () sẽ truy xuất giá trị, nhưng chỉ khi hoạt động hoàn tất. Anh ta sẽ không biết với một cuộc gọi mù quáng .get ()
portfoliobuilder

4

Nếu bạn muốn giá trị từ phương thức gọi, thì nó phải đợi luồng kết thúc, điều này làm cho việc sử dụng luồng hơi vô nghĩa.

Để trả lời trực tiếp câu hỏi của bạn, giá trị có thể được lưu trữ trong bất kỳ đối tượng có thể thay đổi nào cả phương thức gọi và luồng đều có tham chiếu đến. Bạn có thể sử dụng bên ngoàithis , nhưng điều đó sẽ không đặc biệt hữu ích ngoài các ví dụ tầm thường.

Một lưu ý nhỏ về mã trong câu hỏi: Mở rộng Threadthường là kiểu kém. Thực sự mở rộng các lớp một cách không cần thiết là một ý tưởng tồi. Tôi nhận thấy bạn runphương pháp được đồng bộ hóa vì một số lý do. Bây giờ vì đối tượng trong trường hợp này là Threadbạn có thể can thiệp vào bất cứ điều gì Threadsử dụng khóa của nó (trong triển khai tham chiếu, một cái gì đó để làm với join, IIRC).


"sau đó nó nên đợi cho đến khi luồng kết thúc, điều này làm cho việc sử dụng các luồng hơi vô nghĩa" điểm tuyệt vời!
likejudo

4
Nó thường là vô nghĩa, nhưng trong Android, bạn không thể thực hiện yêu cầu mạng tới máy chủ trên Chuỗi chính (để giữ cho ứng dụng phản hồi), vì vậy bạn sẽ phải sử dụng một chuỗi mạng. Có những trường hợp bạn cần kết quả trước khi ứng dụng có thể tiếp tục.
Udi Idan

2

Từ Java 8 trở đi chúng ta có CompletableFuture. Trong trường hợp của bạn, bạn có thể sử dụng phương pháp cung cấp để nhận kết quả sau khi thực thi.

Xin vui lòng tìm một số giới thiệu. tại đây https://www.baeldung.com/java-completablefuture#Processing

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

   completableFuture.get() //gives you the value

1

Sử dụng Tương lai được mô tả trong các câu trả lời ở trên thực hiện công việc, nhưng ít đáng kể hơn một chút như f.get (), chặn luồng cho đến khi nó nhận được kết quả, điều này vi phạm đồng thời.

Giải pháp tốt nhất là sử dụng Guava's ListenableFuture. Một ví dụ :

    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

Với những sửa đổi nhỏ đối với mã của bạn, bạn có thể đạt được nó theo cách chung chung hơn.

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

Giải pháp :

  1. Tạo một chuỗi Handlertrong giao diện người dùng, được gọi làresponseHandler
  2. Khởi tạo điều này Handlertừ Looperluồng giao diện người dùng.
  3. Trong HandlerThread, đăng thông báo về điều nàyresponseHandler
  4. handleMessgaehiển thị Toastgiá trị nhận được từ tin nhắn. Đối tượng Message này là chung và bạn có thể gửi các loại thuộc tính khác nhau.

Với cách tiếp cận này, bạn có thể gửi nhiều giá trị đến chuỗi giao diện người dùng tại các thời điểm khác nhau. Bạn có thể chạy (đăng) nhiều Runnableđối tượng trên này HandlerThreadvà mỗi đối tượng Runnablecó thể đặt giá trị trong Messageđối tượng, có thể được nhận bởi UI Thread.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.