使用RxJava如何执行具有多个依赖项的异步任务

时间:2017-06-30 17:20:07

标签: java rx-java2

我正在学习rxjava并转换我的一些代码库以了解它是如何工作的。目前,我正在尝试使用Observables或Completables来完成执行任务(任务执行没有相关的返回值)和依赖关系,如:

执行

任务:A,B,C,D,E

  • B取决于A
  • C取决于B,D
  • E取决于D

因此任务执行可能如下所示:

execute A, D  
D completes -> execute E  
A completes -> execute B  
B completes -> execute C (B, D both completed)  

问题

  • 接受一个任意的依赖图就像是rxjava的一个很好的用例?
  • 这甚至是observables / completables的用例吗?
  • 如果是这样的话:实现这种行为的技术是什么?

3 个答案:

答案 0 :(得分:1)

您可以使用concat()merge()运算符来实现这一目标。

这是怎么做:

package rxtest;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.schedulers.Schedulers;


public class RxJavaDagTest {
    private static final Logger logger = LoggerFactory.getLogger(RxJavaDagTest.class);
    private static Executor customExecutor = Executors.newFixedThreadPool(20);

    @Test
    public void stackOverflowTest() {
        Observable<Character> a = createObservable('A', 100);
        Observable<Character> b = createObservable('B', 200);
        Observable<Character> c = createObservable('C', 500);
        Observable<Character> d = createObservable('D', 150);
        Observable<Character> e = createObservable('E', 200);

        logger.info("BEGIN");

        // As Observable for D is referred at two places in the graph, it needs to be cached to not to execute twice
        Observable<Character> dCached = d.cache();

        Observable.merge(
                Observable.concat(
                        Observable.merge(
                                Observable.concat(a, b),
                                dCached),
                        c),
                Observable.concat(dCached, e))
                .toBlocking()
                .subscribe(i -> logger.info("Executed : " + i));

        logger.info("END");
    }

    private Observable<Character> createObservable(char c, int sleepMs) {
        Observable<Character> single = Observable.just(c)
                .flatMap(i -> Observable.<Character> create(s -> {
                    logger.info("onSubscribe Start Executing : {}", i);
                    sleep(sleepMs);
                    s.onNext(Character.valueOf(i));
                    s.onCompleted();
                }).subscribeOn(Schedulers.from(customExecutor)));
        return single;
    }

    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        }
        catch (InterruptedException e) {
        }
    }
}

输出将是:

20:47:05.633 [main] INFO rxtest.RxJavaDagTest BEGIN
20:47:05.745 [pool-1-thread-1] INFO rxtest.RxJavaDagTest onSubscribe Start Executing : A
20:47:05.748 [pool-1-thread-2] INFO rxtest.RxJavaDagTest onSubscribe Start Executing : D
20:47:05.849 [main] INFO rxtest.RxJavaDagTest Executed : A
20:47:05.850 [pool-1-thread-3] INFO rxtest.RxJavaDagTest onSubscribe Start Executing : B
20:47:05.899 [main] INFO rxtest.RxJavaDagTest Executed : D
20:47:05.899 [main] INFO rxtest.RxJavaDagTest Executed : D
20:47:05.899 [pool-1-thread-4] INFO rxtest.RxJavaDagTest onSubscribe Start Executing : E
20:47:06.051 [main] INFO rxtest.RxJavaDagTest Executed : B
20:47:06.051 [pool-1-thread-5] INFO rxtest.RxJavaDagTest onSubscribe Start Executing : C
20:47:06.100 [main] INFO rxtest.RxJavaDagTest Executed : E
20:47:06.552 [main] INFO rxtest.RxJavaDagTest Executed : C
20:47:06.552 [main] INFO rxtest.RxJavaDagTest END

答案 1 :(得分:0)

  1.   

    接受任意依赖图是一件好事   用例rxjava?

  2. 嗯,基于RxJava文档,它似乎绝对是RxJava的用例

      

    RxJava是Reactive Extensions的Java VM实现:一个库   用于组合异步和基于事件的程序   可观察的序列。

         

    它扩展了观察者模式以支持数据/事件序列   并添加允许您将序列组合在一起的运算符   在声明性地同时抽象出对诸如此类事物的关注   低级线程,同步,线程安全和并发   数据结构。

    1.   

      这甚至是observables / completables的用例吗?

    2. 如果您不需要操作的结果,但只需要知道它是否完成,那么Completable就是好的。

      1.   

        如果是这样的话:实现这种行为的技术是什么?

      2. 我不知道是否有一种特定的技术可以帮助您在RxJava代码中转换问题。

        我会做这样的事情:

        Single.zip(
                    executeA()
                            .subscribeOn(Schedulers.newThread())
                            .andThen(executeB())
                            .toSingleDefault(""),
                    executeD()
                            .subscribeOn(Schedulers.newThread())
                            .andThen(new CompletableSource() {
                                @Override
                                public void subscribe(@NonNull CompletableObserver cs) {
                                    executeE().subscribeOn(Schedulers.newThread())
                                            .subscribe(() -> Log.d("test", "complete E"));
                                    cs.onComplete();
                                }
                            })
                            .toSingleDefault(""),
                    (BiFunction<String, String, Object>) (s, s2) -> s)
                    .flatMapCompletable(o -> CompletableObserver::onComplete)
                    .andThen(executeC())
                    .doOnSubscribe(disposable -> Log.d("test", "start"))
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(() -> Log.d("test", "complete C"));
        

        其中任务按以下方式定义为Completable

        private Completable executeA() {
            return Completable.create(e -> {
                Thread.sleep(4000);
                Log.d("test", "completing A");
                e.onComplete();
            });
        }
        
        private Completable executeB() {
            return Completable.create(e -> {
                Thread.sleep(12000);
                Log.d("test", "completing B");
                e.onComplete();
            });
        }
        
        private Completable executeC() {
            return Completable.create(e -> {
                Thread.sleep(3000);
                Log.d("test", "completing C");
                e.onComplete();
            });
        }
        
        private Completable executeD() {
            return Completable.create(e -> {
                Thread.sleep(10000);
                Log.d("test", "completing D");
                e.onComplete();
            });
        }
        
        private Completable executeE() {
            return Completable.create(e -> {
                Thread.sleep(10000);
                Log.d("test", "completing E");
                e.onComplete();
            });
        }
        

        注意:我不确定用于解决andThen附加到executeD()部分问题的方法。我们不能以这种方式连接D和E:

        executeD().andThen(executeE())
        

        因为否则任务C将在E之后启动,但是我们希望它在D之后启动。这是我创建执行任务C的CompletableSource的方式,同时允许继续执行调用{{1} }。

        另请注意,在此实现中,我有两个订阅:一个用于路径的每一端(C的结尾和E的结尾)。

答案 2 :(得分:0)

这里是使用RxJava创建和运行DAG的通用工具的实现。我并没有声称自己是反应性理论或RxJava方面的专家,并且承认我可能根本没有做得很好。该代码使用Lombok和JOOL消除样板;如果有兴趣,我可以提供普通Java版本。

package reactivedag;

import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import lombok.NonNull;
import lombok.val;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;

import static io.reactivex.Observable.*;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static org.jooq.lambda.Seq.seq;

public class ReactiveDag {

    public static <T> Observable<T> runAsReactiveDag(
            List<T> ts,
            Function<T, List<T>> tPredsFn,
            Function<T, List<T>> tSuccsFn,
            Consumer<T> jobLogic) {
        val ctx = new Context<T>(newFixedThreadPool(ts.size()), tPredsFn, tSuccsFn, jobLogic, new HashMap<>());

        val dag = seq(ts)
                .map(t -> {
                    Observable<T> pred = createObservable(t, ctx);
                    List<T> succTs = ctx.tSuccsFn.apply(t);
                    return createAndMergeDag(succTs, pred, ctx);
                })
                .flatMap(Optional::stream)
                .findSingle()
                .orElseThrow(() -> new RuntimeException("No DAG was created"));
        dag.subscribe(x -> {}, x -> {}, () -> {
            ctx.executor.shutdown();
        });
        return dag;
    }

    private static class Context<T> {
        @NonNull final ExecutorService executor;
        @NonNull final Function<T,List<T>> tPredsFn;
        @NonNull final Function<T, List<T>> tSuccsFn;
        @NonNull final Consumer<T> jobLogic;
        @NonNull final Map<T, List<Observable<T>>> deferredMerges;

        Context(ExecutorService executor, Function<T, List<T>> tPredsFn, Function<T, List<T>> tSuccsFn, Consumer<T> jobLogic, Map<T, List<Observable<T>>> deferredMerges) {
            this.executor = executor;
            this.tPredsFn = tPredsFn;
            this.tSuccsFn = tSuccsFn;
            this.jobLogic = jobLogic;
            this.deferredMerges = deferredMerges;
        }
    }

    private static <T> Observable<T> createObservable(T t, Context<T> ctx) {
        return just(t)
                .flatMap(tee -> Observable.<T> create(emitter -> {
                    ctx.jobLogic.accept(t);
                    emitter.onNext(t);
                    emitter.onComplete();

                }).subscribeOn(Schedulers.from(ctx.executor)));
    }

    private static <T> Optional<Observable<T>> handleSuccessor(
        Observable<T> upstreamDag,
        T succT,
        int numUpstreamNodes,
        Context<T> ctx) {

        if (numUpstreamNodes == 1) {
            val newUpstreamDag = concat(upstreamDag, createObservable(succT, ctx));
            val newSuccTs = ctx.tSuccsFn.apply(succT);
            return createAndMergeDag(newSuccTs, newUpstreamDag, ctx);

        } else if (numUpstreamNodes > 1) {
            //this successor will have to be merged: either add to deferrals or merge now...
            List<Observable<T>> deferred = ctx.deferredMerges.getOrDefault(succT, new ArrayList<>());
            if (deferred.size() < numUpstreamNodes - 1) {
                //not all merge partners are constructed yet: add to deferrals
                deferred.add(upstreamDag);
                ctx.deferredMerges.put(succT, deferred); //often redundant, benignly
                return Optional.empty();
            } else {
                //ready to merge: merge the current and all deferred upstreams, remove the deferral entry, and
                //continue building the downstream recursively
                deferred.add(upstreamDag);
                val mergedUpstream = merge(deferred);
                ctx.deferredMerges.remove(succT);
                return handleSuccessor(mergedUpstream, succT, 1, ctx);
            }
        } else {
            throw new RuntimeException("successor " + succT + " is not expected to have zero predecessors");
        }

    }

    private static <T> Optional<Observable<T>> createAndMergeDag(
            List<T> ts,
            Observable<T> upstreamDag,
            Context<T> ctx) {

            if (ts.isEmpty())
                return Optional.of(upstreamDag);

            val maybeWrappedUpstreamDag = ts.size() > 1
                    ? upstreamDag.cache()
                    : upstreamDag;

            val observables = seq(ts)
                .map(succT ->
                    handleSuccessor(maybeWrappedUpstreamDag, succT, ctx.tPredsFn.apply(succT).size(), ctx))
                .flatMap(Optional::stream)
                .toList();

            return observables
                .isEmpty()
                    ? Optional.empty()
                    : Optional.of(observables.size() > 1
                    ? merge(observables)
                    : observables.get(0));
    }
}

我正在使用此工具在稍微复杂的模型中处理所有对象,它大大简化了任务。对于OP的问题来说,这太过分了,但是为了说明起见,这是基于上面的ReactiveDag类的解决方案:

package reactivedag;

import lombok.val;
import org.apache.logging.log4j.Logger;

import static reactivedag.ReactiveDag.runAsReactiveDag;
import static java.lang.Thread.sleep;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.apache.logging.log4j.LogManager.getLogger;

public class Sample {
    private static final Logger log = getLogger();

    static public void main(String[] args) {
        val dag = runAsReactiveDag(asList('A', 'D'),
            c -> {
                //predecessor getter function
                switch (c) {
                    case 'B': return asList('A');
                    case 'C': return asList('B', 'D');
                    case 'E': return asList('D');
                    default: return emptyList();
                }
            }, c -> {
                //successor getter function
                switch (c) {
                    case 'A': return asList('B');
                    case 'B': return asList('C');
                    case 'D': return asList('C', 'E');
                    default: return emptyList();
                }
            }, c -> {
                //job logic
                log.info("Starting " + c);
                try {
                    sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info("Ending " + c);
            }
        );
    }
}