Rxandroid What's the difference between SubscribeOn and ObserveOn - java

I am just learning Rx-java and Rxandroid2 and I am just confused what is the major difference between in SubscribeOn and ObserveOn.

SubscribeOn specify the Scheduler on which an Observable will operate.
ObserveOn specify the Scheduler on which an observer will observe this Observable.
So basically SubscribeOn is mostly subscribed (executed) on a background thread ( you do not want to block the UI thread while waiting for the observable) and also in ObserveOn you want to observe the result on a main thread...
If you are familiar with AsyncTask then SubscribeOn is similar to doInBackground method and ObserveOn to onPostExecute...

In case you find the above answer full of jargons:
tl;dr
Observable.just("Some string")
.map(str -> str.length())
.observeOn(Schedulers.computation())
.map(length -> 2 * length)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(---)
Observe an observable... perform the map function in an IO thread (since we are "subscribingOn" that thread)... now switch to a Computation Thread and perform map(length -> 2 * length) function... and finally make sure you Observe the output on (observeOn()) Main thread.
Anyway,
observeOn() simply changes the thread of all operators further Downstream. People usually have this misconception that observeOn also acts as upstream, but it doesn't.
The below example will explain it better...
Observable.just("Some string") // UI
.map(str -> str.length()) // UI
.observeOn(Schedulers.computation()) // Changing the thread
.map(length -> 2 * length) // Computation
.subscribe(---) // Computation
subscribeOn() only influences the thread which is going to be used when Observable is going to get subscribed to and it will stay on it downstream.
Observable.just("Some String") // Computation
.map(str -> str.length()) // Computation
.map(length -> 2 * length) // Computation
.subscribeOn(Schedulers.computation()) // -- changing the thread
.subscribe(number -> Log.d("", "Number " + number)); // Computation
Position does not matter (subscribeOn())
Why?
Because it affects only the time of subscription.
Methods that obey the contact with subscribeOn
-> Basic example : Observable.create
All the work specified inside the create body will run on the thread specified in subscribeOn.
Another example: Observable.just,Observable.from or Observable.range
Note: All those methods accept values, so do not use blocking methods to create those values, as subscribeOn won't affect it.
If you want to use blocking functions, use
Observable.defer(() -> Obervable.just(blockingMenthod())));
Important Fact:
subscribeOn does not work with Subjects
Multiple subscribeOn:
If there are multiple instances of subscribeOn in the stream, only the first one has a practical effect.
Subscribe & subscribeOn
People think that subscribeOn has something to do with Observable.subscribe, but it doesn't have anything special to do with it.
It only affects the subscription phase.
Source : Tomek Polański (Medium)

Summary
Use observeOn to set threads for callbacks "further down the stream (below it)", such as code blocks inside doOnNext or map.
Use subscribeOn to set threads for initializations "upstream (above it)", such as doOnSubscribe, Observable.just or Observable.create.
Both methods can be called multiple times, with each call overwriting previous ones. Position matters.
Let's walk through this topic with an example: we want to find the length of the string "user1032613". This is not an easy task for computers, so it's only natural that we perform the intense calculation in a background thread, to avoid freezing the app.
observeOn
We can call observeOn as many times as we like, and it controls which thread all callbacks below it will run. It's easy to use, and works just as you'd expect.
For example, we will show a progress bar on the main UI thread, then do intensive/blocking operations in another thread, then come back to the main UI thread to update the result:
Observable.just("user1032613")
.observeOn(mainThread) // set thread for operation 1
.doOnNext {
/* operation 1 */
print("display progress bar")
progressBar.visibility = View.VISIBLE
}
.observeOn(backThread) // set thread for operation 2 and 3
.map {
/* operation 2 */
print("calculating")
Thread.sleep(5000)
it.length
}
.doOnNext {
/* operation 3 */
print("finished calculating")
}
.observeOn(mainThread) // set thread for operation 4
.doOnNext {
/* operation 4 */
print("hide progress bar and display result")
progressBar.visibility = View.GONE
resultTextView.text = "There're $it characters!"
}
.subscribe()
In the above example, /* operation 1 */ is ran in the mainThread because we set it using observeOn(mainThread) on the line right above it; then we switch to backThread by calling observeOn again, so /* operation 2 */ will run there. Because we didn't change it before chaining /* operation 3 */, it will run in the back thread as well, just like /* operation 2 */; finally we call observeOn(mainThread) again, to make sure /* operation 4 */ updates the UI from the main thread.
subscribeOn
So we've learned observeOn sets threads for subsequent callbacks. What else are we missing? Well, the Observable itself, and its methods such as just(), create(), subscribe() and so on, are also code that needs to be executed. This is how objects are passed along the stream. We use subscribeOn to set threads for code related to Observable itself.
If we remove all the callbacks (controlled by observeOn discussed earlier), we are left with the "skeleton code" that will, by default, run on whichever thread the code is written in (probably main thread):
Observable.just("user1032613")
.observeOn(mainThread)
.doOnNext {
}
.observeOn(backThread)
.map {
}
.doOnNext {
}
.observeOn(mainThread)
.doOnNext {
}
.subscribe()
If we aren't happy about this empty skeleton code running on main thread, we can use subscribeOn to change it. For example, maybe the first line Observable.just("user1032613") isn't as simple as creating a stream from my user name - maybe it's a string from the Internet, or perhaps you are using doOnSubscribe for some other intensive operations. In that case, you can call subscribeOn(backThread) to put some of the code in another thread.
Where to put subscribeOn
At the time of writing this answer, there are some misconceptions saying "only call it once", "position does not matter", and "if you call it multiple times, only the first time counts". After lots of researches and experiments, it turns out subscribeOn can be usefully called multiple times.
Because Observable uses Builder Pattern (fancy name for "chaining methods one after another"), subscribeOn is applied in reverse order. Therefore, this method sets the thread for code above it, exactly the opposite of observeOn.
We can experiment this using doOnSubscribe method. This method is triggered on the subscription event, and it runs on the thread set by subscribeOn:
Observable.just("user1032613")
.doOnSubscribe {
print("#3 running on main thread")
}
.subscribeOn(mainThread) // set thread for #3 and just()
.doOnNext {
}
.map {
}
.doOnSubscribe {
print("#2 running on back thread")
}
.doOnNext {
}
.subscribeOn(backThread) // set thread for #2 above
.doOnNext {
}
.doOnSubscribe {
print("#1 running on default thread")
}
.subscribe()
It might be easier to follow the logic, if you read the above example from bottom to top, just like how Builder Pattern executes the code.
In this example, the first line Observable.just("user1032613") is run in the same thread as print("#3") because there are no more subscribeOn in-between them. This creates the illusion of "only the first call matters" for people who only care about code inside just() or create(). This quickly falls apart once you start doing more.
Footnote:
Threads and print() functions in the examples are defined, for brevity, as follows:
val mainThread = AndroidSchedulers.mainThread()
val backThread = Schedulers.computation()
private fun print(msg: String) = Log.i("", "${Thread.currentThread().name}: $msg")

If someone finds rx java description hard to understand (as me for example), here is pure java explanation:
subscribeOn()
Observable.just("something")
.subscribeOn(Schedulers.newThread())
.subscribe(...);
Is equivalent of:
Observable observable = Observable.just("something");
new Thread(() -> observable.subscribe(...)).start();
Because Observable emits values on subscribe() and here subscribe() goes in the separate thread, the values are also emitted in the same thread as subscribe(). This is why it works "upstream" (influences the thread for the previous operations) and "downstream".
observeOn()
Observable.just("something")
.observeOn(Schedulers.newThread())
.subscribe(...);
Is equivalent of:
Observable observable = Observable.just("something")
.subscribe(it -> new Thread(() -> ...).start());
Here Observable emits values in the main thread, only the listener method is executed in the separate thread.

When you subscribe to an observable, a flow starts that works its way up to the top of chain and then back down again. The subscribe part is relevant to the upward chaining and the observe part is relevant to the downward chaining.
Once the top of the chain is reached, the subscription phase has essentially completed. Events start to be emitted and the downward chain of maps, filters etc are invoked.
SubscribeOn influences subscription calls above its placement, for example doOnSubscribe.
ObserveOn influences observation calls below its placement, for example, doOnNext, map, flatmap etc.
Both will change the thread that is used to continue the flow either upward or downward.
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.CountDownLatch;
public class SubscribeVsObserveOn {
public static void main(String[] args) throws InterruptedException {
System.out.println("Ordinal 0: " + Thread.currentThread().getName());
final CountDownLatch latch = new CountDownLatch(1);
Observable
.just("a regular string.")
.doOnSubscribe(disposable ->
System.out.println("Ordinal 2: " + Thread.currentThread().getName()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.doOnNext(s ->
System.out.println("Ordinal 3: " + Thread.currentThread().getName()))
.map(s -> s)
.doOnSubscribe(disposable ->
System.out.println("Ordinal 1: " + Thread.currentThread().getName()))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.doOnNext(s ->
System.out.println("Ordinal 4: " + Thread.currentThread().getName()))
.map(s -> s)
.subscribe(s -> latch.countDown());
latch.await();
}
}
Here is the output:
Ordinal 0: main
Ordinal 1: RxNewThreadScheduler-1
Ordinal 2: RxNewThreadScheduler-2
Ordinal 3: RxNewThreadScheduler-3
Ordinal 4: RxNewThreadScheduler-4

This answer is nothing new, I just want to clarify a little bit more.
Let's assume that we have two threads.
val pool1 = Executors.newCachedThreadPool { runnable -> Thread(runnable, "Thread 1") }
val pool2 = Executors.newCachedThreadPool { runnable -> Thread(runnable, "Thread 2") }
As the answers described, observeOn will set Downstream, and subscribeOn will set Upstream. But what if both of them was used? For check this, I added logs line by line.
Observable.just("what if use both")
.doOnSubscribe { Log.d("Thread", "both, doOnSubscribe A " + Thread.currentThread().name) }
.doOnNext { Log.d("Thread", "both, doOnNext A " + Thread.currentThread().name) }
.map {
Log.d("Thread", "both, map A " + Thread.currentThread().name)
it + " A"
}
// observeOn
.observeOn(Schedulers.from(pool1))
.doOnSubscribe { Log.d("Thread", "both, doOnSubscribe B " + Thread.currentThread().name) }
.doOnNext { Log.d("Thread", "both, doOnNext B " + Thread.currentThread().name) }
.map {
Log.d("Thread", "both, map B " + Thread.currentThread().name)
it + " B"
}
// subscribeOn
.subscribeOn(Schedulers.from(pool2))
.doOnSubscribe { Log.d("Thread", "both, doOnSubscribe C " + Thread.currentThread().name) }
.doOnNext { Log.d("Thread", "both, doOnNext C " + Thread.currentThread().name) }
.map {
Log.d("Thread", "both, map C " + Thread.currentThread().name)
it + " C"
}
// observeOn main
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { Log.d("Thread", "main " + Thread.currentThread().name) }
.subscribe(
{ result -> Log.d("Thread", "main subscribe " + Thread.currentThread().name)}
, { error -> {} }
)
The result is:
both, doOnSubscribe C main
both, doOnSubscribe A Thread 2
both, doOnSubscribe B Thread 2
both, doOnNext A Thread 2
both, map A Thread 2
both, doOnNext B Thread 1
both, map B Thread 1
both, doOnNext C Thread 1
both, map C Thread 1
main main
main subscribe main
result: what if use both A B C
As you can see, doOnSubscribe called first, from bottom to top. That means subscribe has priority over other operators, so the first thread which handles first code was Thread 2.
And then other operators was called, line by line. After observeOn, thread was changed to Thread 1. Then, just before calling subscribe, observeOn was called again, for change thread to main thread. (Don't care about AndroidSchedulers, it is just a kind of scheduler)
TL;DR;
First path, subscribeOn called first, from bottom to top.
Second path, observeOn called, from top to bottom, along with other codes.
Behavior was same on both RxJava2 and RxJava3

Related

Reactive programming (Reactor) : Why main thread is stuck?

I'm learning Reactive programming with project-reactor.
I have the following test case:
#Test
public void createAFlux_just() {
Flux<String> fruitFlux = Flux.just("apple", "orange");
fruitFlux.subscribe(f -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(f);
});
System.out.println("hello main thread");
}
By executing the test it seems that the main thread is stuck for 5 seconds.
I would expect that the subscribed consumer should run asynchronously in its own thread, that is, the subscribe invoke should return immediately to the main thread and consequently the hello main thread should print instantly.
The main thread is stuck because the subscription happens on the main thread. If you want it to run asynchronously, you need to the subscription to happen on a thread other than main. You could do this as:
#Test
public void createAFlux_just() {
Flux<String> fruitFlux = Flux.just("apple", "orange");
fruitFlux.subscribeOn(Schedulers.parallel()).subscribe(f -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(f);
});
System.out.println("hello main thread");
}
Note: I have used the parallel thread pool. You could use whatever pool you like. Reactor's pipelines are executed on the calling thread by default (unlike CompletableFuture<T> which runs in the ForkJoin pool by default).
This behavior would be the case if you had an observable (Flux) that was asynchronous. You chose to use a Flux with two readily available values by using the just method. They were passed to the subscription object right away since they were immediately available.
from spring.io documentation
The Threading Model
Reactor operators generally are concurrent agnostic: they don’t impose a particular threading model and just run on the Thread on which their onNext method was invoked.
The Scheduler abstraction
In Reactor, a Scheduler is an abstraction that gives the user control about threading. A Scheduler can spawn Worker which are conceptually Threads, but are not necessarily backed by a Thread (we’ll see an example of that later). A Scheduler also includes the notion of a clock, whereas the Worker is purely about scheduling tasks.
so you should subscribe on different thread by subscribeOn method and the Thread.sleep(5000) will sleep thread of the scheduler. You can see more examples like this one in the documentation.
Flux.just("hello")
.doOnNext(v -> System.out.println("just " + Thread.currentThread().getName()))
.publishOn(Scheduler.boundedElastic())
.doOnNext(v -> System.out.println("publish " + Thread.currentThread().getName()))
.delayElements(Duration.ofMillis(500))
.subscribeOn(Schedulers.elastic())
.subscribe(v -> System.out.println(v + " delayed " + Thread.currentThread().getName()));

RX Java - different behaviour of subscribeOn and observeOn

I'm new to RX java and I've been experimenting with observeOn and subscribeOn methods. I've read that the difference between them is that they affect the whole chain(subscribeOn) or only the part of the chain after setting a scheduler(observeOn). So why the code below executes fine (prints the current thread):
Observable obs = Observable.from(Arrays.asList("element", "2nd element"));
obs.observeOn(Schedulers.newThread())
.map(x -> x.toString().toUpperCase())
.subscribe(x -> System.out.println("NT:" + Thread.currentThread().getName() + x));
while this code doesn't print anything:
Observable obs = Observable.from(Arrays.asList("element", "2nd element"));
obs.subscribeOn(Schedulers.newThread())
.map(x -> x.toString().toUpperCase())
.subscribe(x -> System.out.println("NT:" + Thread.currentThread().getName() + x));
Are you sure that this code doesn't print anything?
I tried this code:
Observable obs = Observable.from(Arrays.asList("element", "2nd element"));
obs.subscribeOn(Schedulers.newThread())
.map(x -> x.toString().toUpperCase())
.subscribe(x -> System.out.println("NT:" + Thread.currentThread().getName() + x));
Thread.sleep(5000);
Output:
NT:RxNewThreadScheduler-1ELEMENT
NT:RxNewThreadScheduler-12ND ELEMENT
Maybe you forgot to sleep or do some another work to make application wait for completion of new RxJava threads?
Basically, an observable (more specifically, a cold observable) is a delayed calculation. So, .subscribeOn() defined on which scheduler/thread this calculation will be done, and .observeOn() defines on which scheduler/thread you will receive its result. For instance, network request should run on Schedulers.io which you specify by adding .subscribeOn(Schedulers.io) and you want to display results on main thread by specifying .observeOn(AndroidShedulers.main)

Code adapted to RX Completable not blocking onSubscribe thread

I have some legacy, non-RX code that does some networking work by spawning a new Thread.
When the work finishes, it invokes one method on a callback.
I don't have control over the thread this code runs on. It's legacy, and it spawns a new Thread by itself.
This can be simplified in something like:
interface Callback {
void onSuccess();
}
static void executeRequest(String name, Callback callback) {
new Thread(() -> {
try {
System.out.println(" Starting... " + name);
Thread.sleep(2000);
System.out.println(" Finishing... " + name);
callback.onSuccess();
} catch (InterruptedException ignored) {}
}).start();
}
I want to convert this to an RX Completable. To do so I use Completable#create().
The implementation of the CompletableEmitter calls the executeRequest passing of implementation of the Callback that signals when the request has finished.
I also print a log trace when subscribed to help me debugging.
static Completable createRequestCompletable(String name) {
return Completable.create(e -> executeRequest(name, e::onComplete))
.doOnSubscribe(d -> System.out.println("Subscribed to " + name));
}
This works as expected. The Completable completes only after the "request" finishes and the callback is invoked.
Problem is that when subscribing to this these completables in a trampoline scheduler, it does not wait for the first request to finish before subscribing to the second request.
This code:
final Completable c1 = createRequestCompletable("1");
c1.subscribeOn(Schedulers.trampoline()).subscribe();
final Completable c2 = createRequestCompletable("2");
c2.subscribeOn(Schedulers.trampoline()).subscribe();
Outputs:
Subscribed to 1
Starting... 1
Subscribed to 2
Starting... 2
Finishing... 1
Finishing... 2
As you see, it subscribes to the second Completable before the first Completable has completed, even if I'm subscribing in trampoline.
I'd like to queue the completables, so that the second waits for the first to finish, outputting this:
Subscribed to 1
Starting... 1
Finishing... 1
Subscribed to 2
Starting... 2
Finishing... 2
I'm sure the problem is related to the work being done in a worker thread. If the implementation of the Completable does not spawn a new thread it works as expected.
But this is legacy code and what I'm trying to do is adapting it to RX without modifying.
NOTE: the requests are executed in different points of the program - I cannot use andThen or concat to implement the serialised execution.
I have managed to execute the Completables sequentially by explicitly blocking the subscription Thread with a Latch.
But I don't think this is the idiomatic way to do it in RX, and I still don't understand why I need to do this and the Thread is not blocked until the Completable completes.
static Completable createRequestCompletable(String name) {
final CountDownLatch latch = new CountDownLatch(1);
return Completable.create(e -> {
executeRequest(name, () -> {
e.onComplete();
latch.countDown();
});
latch.await();
})
.doOnSubscribe(disposable -> System.out.println("Subscribed to " + name));
}

RxJava doesn't work in Scheduler.io() thread

The problem in that: I have Observable and Subscriber. I try to launch Observable in .io() thread, because it works with files and zip archivers (I won't show the code - is too large), but Observable do nothing!:
Observable<Double> creatingObservable = getCreatingObservable(image);
Subscriber<Double> creatingSubscriber = getCreatingSubscriber();
creatingObservable
.subscribeOn(Schedulers.io())
.subscribe(creatingSubscriber);
If I launch code without the subscribeOn() - all work. What is the problem and how to solve it
P.S. System.out.println() doesn't work too. Problem have all Scheduler's threads.
It seems the problem is that the main thread terminated before creatingObservable could emit any values.
The simple solution: make the main thread wait long enough to enable creatingObservable to emit/complete.
Observable<Double> creatingObservable = getCreatingObservable(image);
Subscriber<Double> creatingSubscriber = getCreatingSubscriber();
creatingObservable
.subscribeOn(Schedulers.io())
.subscribe(creatingSubscriber);
Thread.sleep(5000); //to wait 5 seconds while creatingObservable is running on IO thread
Try this one:
Subscriber<Double> creatingSubscriber = getCreatingSubscriber();
Observable.defer(new Func0<Observable<Double>>() {
#Override
public Observable<Double> call() {
return getCreatingObservable(image);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(creatingSubscriber);
Don't forget to add:
compile 'io.reactivex:rxandroid:1.2.1'
From here: https://github.com/ReactiveX/RxAndroid
Explanation
getCreatingObservable(image); - most probably you use some operators which do 'hard' work in moment of call.
For example:
Observable.just(doSomeStuff())
.subscribeOn(...)
.observeOn(...)
So, the execution process will be:
1). Calculate doSomeStuff()
2). Pass result to Observable.just()
3). And only passing you are applying schedulers
In other words, you are doing 'hard' work firstly, and then applying schedulers.
That's why you need to use Observable.defer()
For more explanation, please read this article of Dan Lew:
http://blog.danlew.net/2014/10/08/grokking-rxjava-part-4/
Section Old, Slow Code
In this case you app create observable just once. You may try to either use
Observable.defer(()-> creatingObservable) so .defer operator will force observable creation every time.
Observable.defer(new Func0<Observable<Double>>() {
#Override
public Observable<Double> call() {
return getCreatingObservable();
}
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(getCreatingSubscriber);

Generate infinite sequence of Natural numbers using RxJava

I am trying to write a simple program using RxJava to generate an infinite sequence of natural numbers. So, far I have found two ways to generate sequence of numbers using Observable.timer() and Observable.interval(). I am not sure if these functions are the right way to approach this problem. I was expecting a simple function like one we have in Java 8 to generate infinite natural numbers.
IntStream.iterate(1, value -> value +1).forEach(System.out::println);
I tried using IntStream with Observable but that does not work correctly. It sends infinite stream of numbers only to first subscriber. How can I correctly generate infinite natural number sequence?
import rx.Observable;
import rx.functions.Action1;
import java.util.stream.IntStream;
public class NaturalNumbers {
public static void main(String[] args) {
Observable<Integer> naturalNumbers = Observable.<Integer>create(subscriber -> {
IntStream stream = IntStream.iterate(1, val -> val + 1);
stream.forEach(naturalNumber -> subscriber.onNext(naturalNumber));
});
Action1<Integer> first = naturalNumber -> System.out.println("First got " + naturalNumber);
Action1<Integer> second = naturalNumber -> System.out.println("Second got " + naturalNumber);
Action1<Integer> third = naturalNumber -> System.out.println("Third got " + naturalNumber);
naturalNumbers.subscribe(first);
naturalNumbers.subscribe(second);
naturalNumbers.subscribe(third);
}
}
The problem is that the on naturalNumbers.subscribe(first);, the OnSubscribe you implemented is being called and you are doing a forEach over an infinite stream, hence why your program never terminates.
One way you could deal with it is to asynchronously subscribe them on a different thread. To easily see the results I had to introduce a sleep into the Stream processing:
Observable<Integer> naturalNumbers = Observable.<Integer>create(subscriber -> {
IntStream stream = IntStream.iterate(1, i -> i + 1);
stream.peek(i -> {
try {
// Added to visibly see printing
Thread.sleep(50);
} catch (InterruptedException e) {
}
}).forEach(subscriber::onNext);
});
final Subscription subscribe1 = naturalNumbers
.subscribeOn(Schedulers.newThread())
.subscribe(first);
final Subscription subscribe2 = naturalNumbers
.subscribeOn(Schedulers.newThread())
.subscribe(second);
final Subscription subscribe3 = naturalNumbers
.subscribeOn(Schedulers.newThread())
.subscribe(third);
Thread.sleep(1000);
System.out.println("Unsubscribing");
subscribe1.unsubscribe();
subscribe2.unsubscribe();
subscribe3.unsubscribe();
Thread.sleep(1000);
System.out.println("Stopping");
Observable.Generate is exactly the operator to solve this class of problem reactively. I also assume this is a pedagogical example, since using an iterable for this is probably better anyway.
Your code produces the whole stream on the subscriber's thread. Since it is an infinite stream the subscribe call will never complete. Aside from that obvious problem, unsubscribing is also going to be problematic since you aren't checking for it in your loop.
You want to use a scheduler to solve this problem - certainly do not use subscribeOn since that would burden all observers. Schedule the delivery of each number to onNext - and as a last step in each scheduled action, schedule the next one.
Essentially this is what Observable.generate gives you - each iteration is scheduled on the provided scheduler (which defaults to one that introduces concurrency if you don't specify it). Scheduler operations can be cancelled and avoid thread starvation.
Rx.NET solves it like this (actually there is an async/await model that's better, but not available in Java afaik):
static IObservable<int> Range(int start, int count, IScheduler scheduler)
{
return Observable.Create<int>(observer =>
{
return scheduler.Schedule(0, (i, self) =>
{
if (i < count)
{
Console.WriteLine("Iteration {0}", i);
observer.OnNext(start + i);
self(i + 1);
}
else
{
observer.OnCompleted();
}
});
});
}
Two things to note here:
The call to Schedule returns a subscription handle that is passed back to the observer
The Schedule is recursive - the self parameter is a reference to the scheduler used to call the next iteration. This allows for unsubscription to cancel the operation.
Not sure how this looks in RxJava, but the idea should be the same. Again, Observable.generate will probably be simpler for you as it was designed to take care of this scenario.
When creating infinite sequencies care should be taken to:
subscribe and observe on different threads; otherwise you will only serve single subscriber
stop generating values as soon as subscription terminates; otherwise runaway loops will eat your CPU
The first issue is solved by using subscribeOn(), observeOn() and various schedulers.
The second issue is best solved by using library provided methods Observable.generate() or Observable.fromIterable(). They do proper checking.
Check this:
Observable<Integer> naturalNumbers =
Observable.<Integer, Integer>generate(() -> 1, (s, g) -> {
logger.info("generating {}", s);
g.onNext(s);
return s + 1;
}).subscribeOn(Schedulers.newThread());
Disposable sub1 = naturalNumbers
.subscribe(v -> logger.info("1 got {}", v));
Disposable sub2 = naturalNumbers
.subscribe(v -> logger.info("2 got {}", v));
Disposable sub3 = naturalNumbers
.subscribe(v -> logger.info("3 got {}", v));
Thread.sleep(100);
logger.info("unsubscribing...");
sub1.dispose();
sub2.dispose();
sub3.dispose();
Thread.sleep(1000);
logger.info("done");

Categories

Resources