等待并行RX用户完成

时间:2014-08-06 14:38:18

标签: java reactive-programming rx-java

我正在寻找等待异步任务在rx-java中完成的最佳方法。

作为一个常见的例子,有一个函数可以从本地存储中获取一个id列表,然后在远程系统中查询这些ID,然后将远程系统结果合并为一个报告并返回给函数的调用者。由于对远程系统的调用很慢,我们希望它们以异步方式完成,我只想在所有调用都返回并且其结果已经处理后返回。

我发现这样做的唯一可靠方法是轮询订阅以检查它是否已取消订阅。但我的想法似乎并不像是RX' RX'做事的方式!

作为一个例子,我从http://howrobotswork.wordpress.com/2013/10/28/using-rxjava-in-android/中取了一个例子并稍微修改它以使其成为非机器人并显示我的意思。我必须在main()方法中使用以下代码来阻止它立即退出。

while (!subscription.isUnsubscribed()) {
    Thread.sleep(100);
}

下面列出了该示例的完整代码(如果您尝试编译它,则依赖于http://square.github.io/retrofit/

package org.examples;

import retrofit.RestAdapter;
import retrofit.http.GET;
import retrofit.http.Query;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

public class AsyncExample {

    public static void main(String[] args) throws InterruptedException {
        final Subscription subscription = Observable.from("London", "Paris", "Berlin")
                .flatMap(new Func1<String, Observable<WeatherData>>() {
                    @Override
                    public Observable<WeatherData> call(String s) {
                        return ApiManager.getWeatherData(s);
                    }
                })
                .subscribe(
                        new Action1<WeatherData>() {
                            @Override
                            public void call(WeatherData weatherData) {
                                System.out.println(Thread.currentThread().getName() + " - " + weatherData.name + ", " + weatherData.base);
                            }
                        },
                        new Action1<Throwable>() {
                            @Override
                            public void call(Throwable throwable) {
                                System.out.println(Thread.currentThread().getName() + " - ERROR: " + throwable.getMessage());
                            }
                        },
                        new Action0() {
                            @Override
                            public void call() {
                                System.out.println(Thread.currentThread().getName() + " - COMPLETED");
                            }
                        }
                );

        // Have to poll subscription to check if its finished - is this the only way to do it?
        while (!subscription.isUnsubscribed()) {
            Thread.sleep(100);
        }
    }
}

class ApiManager {

    private interface ApiManagerService {
        @GET("/weather")
        WeatherData getWeather(@Query("q") String place, @Query("units") String units);
    }

    private static final RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint("http://api.openweathermap.org/data/2.5")
            .build();
    private static final ApiManagerService apiManager = restAdapter.create(ApiManagerService.class);

    public static Observable<WeatherData> getWeatherData(final String city) {
        return Observable.create(new Observable.OnSubscribe<WeatherData>() {
            @Override
            public void call(Subscriber<? super WeatherData> subscriber) {
                try {
                    System.out.println(Thread.currentThread() + " - Getting " + city);
                    subscriber.onNext(apiManager.getWeather(city, "metric"));
                    subscriber.onCompleted();
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        }).subscribeOn(Schedulers.io());
    }
}

3 个答案:

答案 0 :(得分:5)

如果您正在询问如何将异步Observable转换为同步Observable,您可以使用.toBlocking()然后使用.last()等待Observable完成。例如,在计时器完成之前,以下内容将不会继续虽然计时器在计算线程上执行。

try {
    Observable
            .timer(10, TimeUnit.SECONDS)
            .toBlocking()
            .last();    // Wait for observable to complete. Last item discarded.
} catch (IllegalArgumentException ex) {
    // No items or error.
}

除非绝对必要,否则您可能不应该使用此方法。通常,应用程序终止将由其他一些事件(按键,关闭菜单项单击等)控制。您的网络请求Observables将异步完成,您将在onNext(),onCompleted()和onError()调用中执行操作更新UI或显示错误。

此外,Retrofit的优点在于它内置了对RxJava的支持,并将在后台执行网络请求。要使用该支持,请将接口声明为返回Observable。

interface ApiManagerService {
    @GET("/weather")
    Observable<WeatherData> getWeather(@Query("q") String place, @Query("units") String units);
}

这允许您将getWeatherData()方法简化为:

public static Observable<WeatherData> getWeatherData(final String city) {
    return apiManager.getWeather(city, "metric");
}

答案 1 :(得分:3)

为了同步等待异步观察,我写了一个简单的函数:

public static <T> void runSynchronously(Observable<T> observable) {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicReference<Throwable> exception = new AtomicReference<>();

    observable.subscribe(new Subscriber<T>() {
        @Override
        public void onCompleted() {
            latch.countDown();
        }

        @Override
        public void onError(Throwable e) {
            exception.set(e);
            latch.countDown();
        }

        @Override
        public void onNext(T o) {
        }
    });

    try {
        latch.await();
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    if (exception.get() != null) {
        throw Exceptions.propagate(exception.get());
    }
}

你可以这样使用它:

    Observer<String> sideEffects = new Observer<String>() {
        @Override
        public void onCompleted() {
            System.out.println("onCompleted");
        }

        @Override
        public void onError(Throwable e) {
            System.out.println("onError: " + e.getMessage());
        }

        @Override
        public void onNext(String s) {
            System.out.println("onNext: " + s);
        }
    };

    runSynchronously(
            Observable.just("London", "Paris", "Berlin")
                    .doOnEach(sideEffects)
    );

答案 2 :(得分:1)

在大多数情况下,最好不要阻止方法。尝试尽可能无阻塞。如果您需要在Observable完成后执行某些操作,只需从onCompleted回调调用您的逻辑,或使用doOnCompleted运算符。

如果你确实需要阻止,那么你可以使用Blocking Observable Operators。 大多数运算符都会阻塞,直到Observable完成。

但是,最后可以通过一个简单的System.in.read()来阻止主要方法过早退出。