Reputation: 265
I have a list of tasks I want to perform in parallell, but I want to display the result of the tasks in the same order as the original list. In other words, if I have task list [A,B,C], I do not wish to show B-result before I have shown A-result, but nor do I want to wait until A-task is finished before starting B-task.
Additionally, I want to show each result as soon as possible, in other words, if the tasks finish in the order B, then A, then C, I do not want to show anything when I receive B-result, then show A-result immediately followed by B-result when I receive A-result, then show C-result whenever I receive it.
This is of course not terribly tricky to do by making an Observable for each task, combining them with merge, and subscribing on a computation thread pool, then writing a Subscriber which holds a buffer for any results received out of order. However, the Rx rule of thumb tends to be "there's already an operator for that", so the question is "what is the proper RxJava way to solve this?" if indeed there is such a thing.
Upvotes: 3
Views: 730
Reputation: 70017
It seems you need concatEager
for this task but it is somewhat possible to achieve it with pre 1.0.15 tools and no need for "creating" Observables. Here is an example for that:
Observable<Long> source1 = Observable.interval(100, 100, TimeUnit.MILLISECONDS).take(10);
Observable<Long> source2 = Observable.interval(100, 100, TimeUnit.MILLISECONDS).take(20);
Observable<Long> source3 = Observable.interval(100, 100, TimeUnit.MILLISECONDS).take(15);
Observable<Observable<Long>> sources = Observable.just(source1, source2, source3);
sources.map(v -> {
Observable<Long> c = v.cache();
c.subscribe(); // to cache all
return c;
})
.onBackpressureBuffer() // make sure all source started
.concatMap(v -> v)
.toBlocking()
.forEach(System.out::println);
The drawback is that it retains all values for the whole duration of the sequence. This can be fixed with a special kind of Subject: UnicastSubject
but RxJava 1.x doesn't have one and may not get one "officially". You can, however, look at one of my blog posts and build if for yourself and have the following code:
//...
sources.map(v -> {
UnicastSubject<Long> subject = UnicastSubject.create();
v.subscribe(subject);
return subject;
})
//...
Upvotes: 1
Reputation: 5823
"There's not quite an operator for that". Although, in the 1.0.15-SNAPSHOT build there is an experimental concatEagar()
operator those sounds like it does what you're looking for. Pull request for concatEager
repositories {
maven { url 'https://oss.jfrog.org/libs-snapshot' }
}
dependencies {
compile 'io.reactivex:rxjava:1.0.15-SNAPSHOT'
}
If you want to roll your own temporary solution until concatEager()
gets the nod of approval. You could try something like this:
public Observable<Result> concatEager(final Observable<Result> taskA, final Observable<Result> taskB, final Observable<Result> taskC) {
return Observable
.create(subscriber -> {
final Observable<Result> taskACached = taskA.cache();
final Observable<Result> taskBCached = taskB.cache();
final Observable<Result> taskCCached = taskC.cache();
// Kick off all the tasks simultaneously.
subscriber.add(
Observable
.merge(taskACached, taskBCached, taskCCached)
.subscribe(
result -> { // Throw away result
},
throwable -> { // Ignore errors
}
)
);
// Put the results in order.
subscriber.add(
Observable
.concat(taskACached, taskBCached, taskCCached)
.subscribe(subscriber)
);
});
}
Note that the above code is totally untested. There are probably better ways to do this but this is what first came to mind...
Upvotes: 1