【问题标题】:Returning value from Thread从线程返回值
【发布时间】:2012-02-27 06:23:13
【问题描述】:

我有一个带有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();
}

【问题讨论】:

  • 如果主线程必须等待处理线程完成才能从方法返回,为什么要首先使用处理线程?
  • @JBNizet 我没有包括线程实际执行的复杂性。它正在获取 gps 坐标,所以是的,我确实需要一个线程。
  • 不管线程的复杂程度如何,如果启动它的线程在启动它后立即等待它的结果,那么启动不同的线程是没有意义的:启动线程将被阻塞,就好像它自己做的。
  • @JBNizet 我不太清楚你的意思..你介意用不同的方式解释吗?
  • 一个线程用于能够在后台执行某事,并能够在后台线程执行时执行其他操作。如果你启动一个线程,然后立即阻塞,直到线程停止,你可以自己完成线程完成的任务,它不会有任何区别,只是它会简单得多。

标签: java android multithreading


【解决方案1】:

通常你会这样做

 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 一个线程不能返回一个值(至少在没有回调机制的情况下不能)。您应该像普通类一样引用线程并请求值。

【讨论】:

  • 这真的有效吗?我得到The method getValue() is undefined for the type Thread
  • @pmichna,很好的发现。从t.getValue() 更改为foo.getValue()
  • 是的!做得好! “易变”ftw!与公认的答案不同,这个答案是正确的!
  • @HamzahMalik 确保线程完成使用Thread t = new Thread(foo); t.start(); t.join(); foo.getValue();t.join() 阻塞,直到线程完成。
  • @HariKiran 是正确的,每个线程返回一个独立的值。
【解决方案2】:

您正在寻找的可能是Callable<V> 接口代替Runnable,并使用Future<V> 对象检索值,这也可以让您等到计算出该值。您可以通过ExecutorService 来实现这一点,您可以从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();
}

【讨论】:

    【解决方案3】:

    您可以使用局部最终变量数组。该变量需要是非原始类型,因此您可以使用数组。您还需要同步两个线程,例如使用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.
    }
    

    您也可以像这样使用ExecutorCallable

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

    【讨论】:

    • 嗯...不。此代码不正确。对值的访问未正确同步。
    • 由于 CountDownLatch 的内存一致性保证,我们实际上不需要对值的访问进行显式同步。 value 数组创建发生在 uiThread 启动之前(程序顺序规则),它与将 2 分配给 value[0] (thread start) 同步发生在前面latch.await()(来自 CountDownLatch 的保证)发生在从 value[0] 读取之前(程序顺序规则)。
    • 看来您对 Latch 的看法是正确的! ...在这种情况下,run方法的同步是没有用的。
    • 好点。我必须从 OP 的代码中复制粘贴。已更正。
    • 这里是一个CountDownLatch的例子:developer.android.com/reference/java/util/concurrent/…
    【解决方案4】:

    如果你想要调用方法的值,那么它应该等待线程完成,这使得使用线程有点毫无意义。

    为了直接回答您的问题,该值可以存储在调用方法和线程都具有引用的任何可变对象中。你可以使用外部的this,但除了一些琐碎的例子之外,这不会特别有用。

    关于问题中的代码的一点说明:扩展Thread 通常是糟糕的风格。确实,不必要地扩展类是一个坏主意。我注意到你 run 方法由于某种原因是同步的。现在,由于本例中的对象是Thread,您可能会干扰Thread 使用其锁定的任何内容(在参考实现中,与join,IIRC 有关)。

    【讨论】:

    • "那么它应该等待线程完成,这使得使用线程有点毫无意义" 好点!
    • 这通常没有意义,但在 Android 中,您无法向主线程上的服务器发出网络请求(以保持应用程序响应),因此您必须使用网络线程。在某些情况下,您需要结果才能恢复应用。
    【解决方案5】:

    这个解决方案怎么样?

    它不使用 Thread 类,但它是并发的,并且在某种程度上它完全符合您的要求

    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(),那么调用它的线程只会等到它完成

    【讨论】:

    • 我将提供的代码分为两件事,一是我在 Application 类中执行的池启动(我说的是 android),其次我在需要它的地方使用池。 .另外关于使用 Executors.newFixedThreadPool(2) 我使用了 Executors.newSingleThreadExecutor() ..因为我只需要一次运行一个任务来进行服务器调用...你的答案是完美的@electirc 咖啡谢谢
    • @Electric 你怎么知道什么时候获得价值?我相信原始发布者想在他的方法中返回这个值。使用 .get() 调用将检索该值,但前提是操作完成。他不会通过盲目的 .get() 调用知道
    【解决方案6】:

    使用上述答案中描述的 Future 可以完成这项工作,但不如 f.get() 显着,它会阻塞线程直到它得到结果,这违反了并发性。

    最好的解决方案是使用 Guava 的 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)
            {
    
            }
        };
    

    【讨论】:

    • 你能解释一下这里发生了什么吗? Long 结果从何而来?因为 call() 没有返回任何东西。
    【解决方案7】:

    通过对代码进行少量修改,您可以以更通用的方式实现它。

     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. 在UI Thread中创建一个Handler,称为responseHandler
    2. 从 UI 线程的 Looper 初始化这个 Handler
    3. HandlerThread,在此responseHandler 上发布消息
    4. handleMessgae 显示了一个 Toast ,其中包含从消息中接收到的值。此 Message 对象是通用的,您可以发送不同类型的属性。

    使用这种方法,您可以在不同的时间点向 UI 线程发送多个值。您可以在此HandlerThread 上运行(发布)许多Runnable 对象,并且每个Runnable 可以在Message 对象中设置值,这些值可以由UI 线程接收。

    【讨论】:

      【解决方案8】:

      从 Java 8 开始,我们有了CompletableFuture。 根据您的情况,您可以使用方法supplyAsync 来获取执行后的结果。

      请参考here

          CompletableFuture<Integer> completableFuture
            = CompletableFuture.supplyAsync(() -> yourMethod());
      
         completableFuture.get() //gives you the value
      

      【讨论】:

        【解决方案9】:

        这是一种更简洁的方法,您只需对现有代码进行一些更改。目标是从线程中获取结果。结果不一定是return。相反,使用回调样式获取该结果并进行进一步处理。

        public class Test {
        
          public static void main(String[] args) {
            String str = args[0];
            int count = 0;
        
            Thread t = new Thread(() ->
              someFuncToRun(str, count, (value) -> {
                System.out.println(value);
                return value;
              }));
        
            t.start();
          }
          // Here I even run a recursive method because run things in the 
          // a thread sometime is to delegate those heavy lifting elsewhere
          public static String someFuncToRun(String str, int ctn, Callback<String> p) {
            ++ctn;
            if (ctn == 10) {
              System.out.println("End here");
              return p.cb(str);
            }
            System.out.println(ctn + " times");
            return someFuncToRun(str + " +1", ctn, p);
          }
        }
        
        // The key is here, this allow you to pass a lambda callback to your method 
        // update: use generic to allow passing different type of data
        // you could event make it <T,S> so input one type return another type  
        interface Callback<T> {
            public T cb(T a);
        }
        
        

        【讨论】:

          猜你喜欢
          • 2013-11-06
          • 1970-01-01
          • 2011-01-19
          • 2021-10-24
          • 1970-01-01
          • 2012-07-10
          相关资源
          最近更新 更多