Handler可以返回Future对象吗?

时间:2017-10-26 14:47:11

标签: java android multithreading

我可以从处理程序中获取未来的对象吗?

Handler handler = new Handler(getMainLooper());
Future<String> future = handler.post(new Callable<String>() {
    public String call() throw Exception {
        // run in the main thread
        return askForPassword();
    }
}); // can I do something like this?
String password = future.get(); // wait until finish
// do network things...

我有一个网络线程,我需要向用户询问密码,因为我需要显示一个输入对话框,我必须在主线程上执行此操作,但处理程序无法返回值。

我可以做同样的事情
Handler handler = new Handler(getMainLooper());
String password = null;
handler.post(() -> {
    // run in the main thread
    password = askForPassword();
});
while (password == null) { /*wait until finish*/ }
// do network things...

但这看起来很愚蠢和不方便

2 个答案:

答案 0 :(得分:1)

Handler基本上是异步的,因此无论你放在哪里都不能保证立即运行(此外,你可以postDelayedpostAtTime)。然后很明显,您不能将Handler中的任何值直接返回到发布它的代码。所以你必须解决这个问题。

另一个障碍是,在Java中,你的闭包只能捕获final变量,你也必须解决这个问题。

新API(CompletableFuture

不幸的是,原始Future Java API不适用于组合。如果您仅定位新设备,因此可以使用较新的CompletableFuture,则可以执行以下操作:

CompletableFuture<String> askForPasswordNewApi() {
     // your UI code that creates Future
}

void doNetworkThingNewApi() {
    // some network stuff

    final CompletableFuture<String> passwordFutureWrapper = new CompletableFuture<String>();
    Handler handler = new Handler(getMainLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
            // run in the main thread
            CompletableFuture<String> future = askForPasswordNewApi();
            // bind the real future to the outer one
            future.handle((r, ex) -> {
                if (ex != null)
                    passwordFutureWrapper.completeExceptionally(ex);
                else
                    passwordFutureWrapper.complete(r);
                return 0;
            });
        }
    });

    // wait until finish
    // don't forget to handle InterruptedException here
    String password = passwordFutureWrapper.get(); 

    // do more network things...
}

这个想法很简单:创建可由final捕获的外部passwordFutureWrapper变量Handler,并将此包装器绑定到真实的future

旁注 :如果askForPassword已经返回Future但您无法使用新API,则可能已重新实施类似于CompletableFuture的东西,所以你只需稍微修改一下这个代码就可以将一个未来绑定到另一个未来。

旧API

如果您尚未在代码中使用CompletableFuture,但仍然以某种方式使用基于Future的签名的方法:

Future<String> askForPasswordOldApi() 

你可以更明确地做到这一点:

void doNetworkThingOldApi() {
    // some network stuff

    final CountDownLatch syncLock = new CountDownLatch(1);
    final Future<String>[] futureWrapper = new Future<String>[1];

    Handler handler = new Handler(getMainLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
            // run in the main thread
            final CompletableFuture<String> future = askForPasswordOldApi();
            futureWrapper[0] = future;
            syncLock.countDown();
        }
    });


    String password;
    try {
        // 1 minute should be quite enough for synchronization between threads
        if (!syncLock.await(1, TimeUnit.MINUTES)) {
            // log error, show some user feedback and then stop further processing
            return;
        }
        password = futureWrapper[0].get(); // wait until finish
    } catch (InterruptedException ex) {
        // log error, show some user feedback and then stop further processing
        return;
    } catch (ExecutionException ex) {
        // log error, show some user feedback and then stop further processing
        return;
    }

    // do more network things...
}

这里的想法如下:

  1. 使用单元素数组作为简单容器来解决final - 闭包限制

  2. 使用CountDownLatch确保网络和UI线程之间的同步,即当我们开始使用futureWrapper[0]等待结果时,get不为空。

  3. 更新(图书馆API设计)

    如果您正在设计API并希望通过回调处理具有不同附加方案的登录单个条目,我会使用类似于CompletableFuture的自定义实现:

    public interface ResultHandler<T> {
        void resolve(T result);
    
        void cancel();
    }
    
    
    class ResultHandlerImpl<T> implements ResultHandler<T> {
    
        enum State {
            Empty,
            Resolved,
            Cancelled
        }
    
    
        private final Object _lock = new Object();
        private State _state = State.Empty;
        private T _result;
    
    
        @Override
        public void resolve(T result) {
            synchronized (_lock) {
                if (_state != State.Empty) // don't override current state
                    return;
    
                _result = result;
                _state = State.Resolved;
                _lock.notifyAll();
            }
        }
    
        @Override
        public void cancel() {
            synchronized (_lock) {
                if (_state != State.Empty) // don't override current state
                    return;
    
                _state = State.Cancelled;
                _lock.notifyAll();
            }
        }
    
    
        public boolean isCancelled() {
            synchronized (_lock) {
                return _state == State.Cancelled;
            }
        }
    
        public boolean isDone() {
            synchronized (_lock) {
                return _state == State.Resolved;
            }
        }
    
        public T get() throws InterruptedException, CancellationException {
            while (_state == State.Empty) {
                synchronized (_lock) {
                    _lock.wait();
                }
            }
            if (_state == State.Resolved)
                return _result;
            else
                throw new CancellationException();
        }
    }
    

    我可能会创建接口public但是实现ResultHandlerImpl package-private,因此用户更难以搞清楚实现细节。然后在回调方法中,我将回调ResultHandler作为参数传递(实际上它显然是ResultHandlerImpl):

    public interface LoginCallback {
        void askForPassword(ResultHandler<String> resultHandler);
    }
    

    我的login方法看起来像这样(假设你有私有方法tryRestoreSession,不需要密码和loginWithPassword需要):

    public boolean login(final LoginCallback loginCallback) {
        if (tryRestoreSession()) {
            return true;
        } else {
            final ResultHandlerImpl<String> passwordHandler = new ResultHandlerImpl<>();
    
            Handler handler = new Handler(getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // run in the main thread
                    loginCallback.askForPassword(passwordHandler);
                }
            });
            String password;
            try {
                password = passwordHandler.get();
            } catch (CancellationException e) {
                return false;
            } catch (InterruptedException e) {
                return false;
            }
            return loginWithPassword(password);
        }
    }
    

    我认为这里很重要:

    1. 我认为将回调传递给LoginCallback可以更容易地使用标准Java编写基于异步UI的实现。

    2. ResultHandlerImplcancel方法。因此,如果用户忘记密码,则有一种方法可以取消整个登录过程,而不会遇到后台线程一直等待密码

    3. ResultHandlerImpl使用显式同步和wait / notifyAll在不同线程上的操作之间建立happens-before关系以避免。

    4. ResultHandlerImpl使用wait / notifyAll,因此后台线程在等待用户界面时不会占用CPU(和电池)。

答案 1 :(得分:0)

使用“等待和通知”而不是循环

进行更新

更新了2个同步方法

最后,我最终得到了对象包装器(thansk到@SergGr)和处理程序

class ObjectWrapper<T> {
    T object;
    boolean ready;

    synchronized void set(T object) {
        this.object = object;
        this.ready = true;
        notifyAll();
    }

    T get() {
        while (!ready) { 
            synchronized(this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    return null;
                }        
            }  
        }
        return object;
    }
}

在我的网络线程中

Handler handler = new Handler(getMainLooper());
ObjectWarpper<String> wrapper = new ObjectWarpper<>();
handler.post(() -> wrapper.set(askForPassword()));
String password = wrapper.get();