【问题标题】:Capturing executor for current thread捕获当前线程的执行程序
【发布时间】:2019-12-20 22:25:29
【问题描述】:

我正在使用来自 Guava 的 ListenableFuture,其中一个好处是可以将 Executor 传递给 Futures.addCallback 方法,即要求在给定线程/执行程序上执行回调。

在我的 Android 应用程序中,我希望能够在 UI 线程中启动基于 ListenableFuture 的异步执行,并安排一个也在 UI 线程上执行的回调。因此,我想以某种方式将 UI 线程执行器提交给上面提到的Futures.addCallback 方法。如何实现?

或者,换句话说,我想要一个 UI 线程的执行器。它是否已经在 Android 中可用,或者,如果我必须创建自己的,我该怎么做?

编辑:作为对这个问题的扩展,是否可以做同样的事情,但不仅仅是使用 UI 线程,而是使用任何特定线程,在哪里调用异步方法?

我很高兴知道如何在不诉诸 Android 特定的东西(如 HandlerLooper)的情况下仅使用纯 Java 来实现相同的效果。

【问题讨论】:

  • 无法理解您的问题!什么是执行器?请详细一点!谢谢

标签: java android guava executorservice


【解决方案1】:

我想我已经看到了一些实现。基本思路大致是

class UiThreadExecutor implements Executor {
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    @Override
    public void execute(Runnable command) {
        mHandler.post(command);
    }
}

您可以委托在主线程中运行任何东西,方法是将其传递给主线程的处理程序。

编辑:例如https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit/android/MainThreadExecutor.java

Edit2:您可以配置处理程序,例如SensorManager#registerListener(..., Handler handler) 允许你做。

class HandlerThreadExecutor implements Executor {
    private final Handler mHandler;
    public HandlerThreadExecutor(Handler optionalHandler) {
        mHandler = optionalHandler != null ? optionalHandler : new Handler(Looper.getMainLooper());
    }

    @Override
    public void execute(Runnable command) {
        mHandler.post(command);
    }
}

与使用当前线程的 looper 相比,它的优势在于它明确了您使用的 Looper。在您的解决方案中,您使用任何线程调用 new ExecuteOnCaller() 的 Looper - 这通常不是您稍后运行代码的线程。

我很高兴知道如何在不使用 Handler 和 Looper 等 Android 特定的东西的情况下仅使用纯 Java 来实现相同的效果。

LooperHandler 以及所有这些逻辑背后的消息队列主要由纯 Java 组成。通用解决方案的问题是您无法“注入”代码以运行到线程中。线程必须定期检查某种任务队列,看看是否有东西要运行。

如果你写这样的代码

    new Thread(new Runnable() {
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                System.out.println("Hello");
            }
        }
    }).start();

那么除了不断地打印“Hello”之外,没有办法让该线程做任何其他事情。如果你能做到这一点,那就像在程序代码中动态插入到其他代码的跳转一样。那将是 IMO 的一个糟糕的主意。

    final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                while (true) {
                    Runnable codeToRunInThisThread = queue.take();
                    codeToRunInThisThread.run();
                }
            } catch (InterruptedException ignored) {}
        }
    }).start();

另一方面是一个简单的线程,它在队列中永远循环。线程可以在两者之间执行其他任务,但您必须在代码中添加手动检查。

您可以通过

向它发送任务
    queue.put(new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello!");
        }
    });

这里没有定义特殊的处理程序,但这是 Handler & Looper 在 Android 中所做的核心。 Android 中的Handler 允许您为Message 定义回调,而不仅仅是Runnable

Executors.newCachedThreadPool() 和类似的做大致相同的事情。只有多个线程在单个队列中等待代码。


作为这个问题的扩展,是否可以做同样的事情,但不仅是使用 UI 线程,而是使用任何特定线程,在哪里调用异步方法?

一般的答案是否定的。只有当有办法注入代码以在该线程中运行时。

【讨论】:

  • @Fildor 是相同的,但需要引用 Activity,您不应该在该执行程序中拥有该引用。
  • 谢谢,这肯定回答了这个问题。不过,我想知道更多:是否可以捕获并稍后在当前线程上执行,无论它是否是 UI 线程?我来自 C#,我知道在那里做这件事的方法,如果能为 Java 学习同样的东西会很高兴。
  • @Haspemulator Android 的处理程序(它们在标准 Java 中不存在)具有像 mHandler.postDelayed(command, timeInMilliseconds) 这样的方法,您可以在其中执行某些操作。 “当前线程”是指调用Executor.execute(Runnable) 的线程或最初提交ListenableFuture 的线程?
  • 你是我的英雄。谢谢。
【解决方案2】:

基于@zapl 的 asnwer,这是我的实现,它还回答了编辑(扩展)的问题:https://gist.github.com/RomanIakovlev/8540439

想通了,我也把它放在这里,以防万一有一天链接会烂掉:

package com.example.concurrent;

import android.os.Handler;
import android.os.Looper;

import java.util.concurrent.Executor;

/**
* When the calling thread has a Looper installed (like the UI thread), an instance of ExecuteOnCaller will submit
* Runnables into the caller thread. Otherwise it will submit the Runnables to the UI thread.
*/
public class ExecuteOnCaller implements Executor {

    private static ThreadLocal<Handler> threadLocalHandler = new ThreadLocal<Handler>() {
        @Override
        protected Handler initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null)
            looper = Looper.getMainLooper();
            return new Handler(looper);
        }
    };

    private final Handler handler = threadLocalHandler.get();

    @Override
    public void execute(Runnable command) {
        handler.post(command);
    }
}

我使用它的模式是这样的:

/**
* in SomeActivity.java or SomeFragment.java
*/
Futures.addCallback(myModel.asyncOperation(param), new FutureCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            // handle success
        }

        @Override
        public void onFailure(Throwable throwable) {
            // handle exception
        }
    }, new ExecuteOnCaller());

【讨论】:

    【解决方案3】:

    使用com.google.android.gms.tasks.TaskExecutors.MAIN_THREAD

    使用主应用程序线程的 Executor。

    来源:Android docs

    任务 API 是 Google Play 服务since version 9.0.0 的一部分。

    【讨论】:

    • +1,虽然这需要使用 Google Play 服务。另一方面,这个 API(Tasks)似乎解决了ListenableFuture 的很多问题,特别是关于 Activity 生命周期问题(但奇怪的是不是 Fragment 生命周期问题)。
    【解决方案4】:

    对于 Android UI 线程执行器使用:

    ContextCompat.getMainExecutor(context)

    【讨论】:

      【解决方案5】:

      解决您的问题和扩展问题以创建一个仅在当前线程上运行并避免 Android 类的 Executor:

      class DirectExecutor implements Executor {
         public void execute(Runnable r) {
           r.run();
         }
       }
      

      查看文档:https://developer.android.com/reference/java/util/concurrent/Executor

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-10-16
        • 2012-07-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-20
        • 2017-06-19
        相关资源
        最近更新 更多