How to cancel Observable execution with RxJava once all subscribers gets disposed - android

In my code I am creating an Observable like this -
Observable observable = Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return longlongTask();
}
})
And then subscribing to it as -
Disposable disposable = observable.subscribeOn(Schedulers.single())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(string -> showResult(string)
My problem is even after I dispose my subscriber using disposable.dispose(), the Observable's long running task keeps on running until it finally completes. I would like to know if there is a way for me to stop this longlongTask once there are no subscribers listening to my Observable. Any methods/standard practices any of you have used to tackle this problem will be appreciated.

Related

RxJava — emit on subscribeOn() thread

I have the following code:
Single.create { emitter ->
// I/O thread here
ThirdPartySDK.doSomeAction {
// Main thread here
emitter.onSuccess(someValue)
}
}
.flatMap {
someOtherSingle(it) // Executes on main thread
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({},{})
The ThirdPartySDK.doSomeAction callback posts on main thread, so the emitter will emit on the main thread too, not on the subscribe thread (and if I have some network interactions further in the flatMap, chain will fail).
If I add observeOn(Schedulers.io()) after the first Single, it switches to the correct thread, but is there any way to emit on right thread? I can't modify ThirdPartySDK behaviour.
subscribeOn
The subscribeActual lambda will be invoked on given scheduler
observeOn
Switch thread to given scheduler. Every upstream-onNext call will be called from an ObserveOn-Scheduler-Thread
As you already said, subscribeOn will only invoke the subscribeActual method call on subscribe on given Scheduler-Thread. This does not mean, that the downstream emit will be on the same thread. In your case the onSuccess emit will be called from a different thread (e.g. Database/ Http-ThreadPool etc.).
onSuccess will be called from a unknown thread (in your case main thread). The downstream call will be called from the main-thread. Therefore flatMap is called from the main-thread. Network-calls on the main-thread in the flatMap will probably fail, because it is not allowed to "block" the main-thread.
How to solve this issue?
Just place a observeOn after the Single#create. The main-thread calls onSucess. The observeOn-subscriber will get called from the main-thread. The observeOn-subscriber re-directs onSuccess downstream-call (e.g. flatMap) to given ObserveOn-Scheduler-Thread. Therefore it is given, that flatMap is called from a non main-loop thread.
Example:
#Test
fun wurst() {
val thirdPartySDKImpl = ThirdPartySDKImpl()
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
emitter.onSuccess(it)
}
}
// .subscribeOn(Schedulers.computation())
// move emit from unknown thread to computation thread
.observeOn(Schedulers.computation())
// Single.just will be subscribe from a computation thread
.flatMap { Single.just(123) }
// move onSucess/ onError emit from computation thread to main-thread
.observeOn(AndroidSchedulers.mainThread())
// subscribe onNext / onError will be called from the main-android-thread
.subscribe({}, {})
}
interface ThirdPartySDK {
fun doSomeAction(callback: (v: String) -> Unit)
}
class ThirdPartySDKImpl : ThirdPartySDK {
override fun doSomeAction(callback: (v: String) -> Unit) {
// <- impl-detail ->
callback("whatever")
}
}
NOTE: You do not need a subscribeOn, if the create-lambda does not block or does some cpu heavy stuff. If it only subscribes to a callback, which will be called from a different thread, you do not need subscribeOn.
but is there any way to emit on right thread?
You should not use any concurrency in operators. You would think, you could just do something like:
Single.create<String> { emitter ->
thirdPartySDKImpl.doSomeAction {
Schedulers.io().scheduleDirect {
emitter.onSuccess(it)
}
}
}
But this is not recommended, because you could break the serialized onNext contract^1. This example would make sure, that the onSucess downstream call would happen on expected thread, but cancellation/ unsubscription is not handled and there might be other pitfalls.
If you have a non reactive API and you want to enforce some threading-model I would suggest to wrap the sync. API with an async one and provide proper observeOn/ subscribeOn operators. Later on only use the async API.
interface ThirdPartySDKAsync {
fun doSomeAction(): Single<String>
}
class ThirdPartySDKAsyncImpl(private val sdk: ThirdPartySDK, private val scheduler: Scheduler) :
ThirdPartySDKAsync {
override fun doSomeAction(): Single<String> {
return Single.create<String> { emitter ->
sdk.doSomeAction {
emitter.onSuccess(it)
}
}.observeOn(scheduler)
}
}
Further reading: https://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html
^1 Only one thread a time is allowed to call onNext/onSuccess/onError/onComplete

Completable.fromAction not executing the code in rjava2

Completable.fromAction(() -> startRecording())
.subscribeOn(Schedulers.io())
.subscribe(() -> {
boolean startSuccess = mMediaRecorder.getState() == MediaRecorder.RECORDING_STATE;
if (startSuccess) {
updateView();
startRepeatingTask();
}
},throwable -> {
Logger.info("Record failed with exception" + throwable);
}).dispose();
I am trying to execute code in background using Completable.fromAction but it is not executing the code if I use subscribeOn(Schedulers.io()).
if I remove subscribeOn(Schedulers.io()), it is executing the code in main thread. I want to executing the code in background thread.
People have already highlighted the problem with your code in the comments - you call dispose() on the Disposable that your Completable returns immediately. This means you cancel your Completable before it has even started. Alter your code to store your Disposable in an instance variable, and call dispose() only when you are no longer interested in receiving it completing. This usually happens in a lifecycle callback like onPause or onStop. For example:
public class SomeActivity extends Activity {
private final CompositeDisposable disposables = new CompositeDisposable();
//...
disposables.add(Completable.fromAction(() -> startRecording())
.subscribeOn(Schedulers.io()) //Note: `updateView` implies UI work. Should you also have `observeOn(AndroidSchedulers.mainThread)?
.subscribe(() -> {
boolean startSuccess = mMediaRecorder.getState() == MediaRecorder.RECORDING_STATE;
if (startSuccess) {
updateView();
startRepeatingTask();
}
}, throwable -> {
Logger.info("Record failed with exception" + throwable);
}));
//Later, in some other lifeycle callback when you no longer care about updates...
disposables.clear();
change the .fromAction to .fromCallable
Callables are designed to perform a single emitter, and then complete. Actual doc explanation here.
The .fromAction is a bit different. Docs here.

Defer and fromCallable behavior in RxJava 2.x

I've read some RxJava2 issues about the crash after call dispose() on an Observable here and here. Then I tried to reproduce the crash by the code:
private String simulateHeavyWork() throws InterruptedException {
Thread.sleep(2000);
return "Done";
}
private void myFunc() {
Disposable disposable = Observable.fromCallable(() -> simulateHeavyWork())
.subscribeOn(Schedulers.io())
.subscribe(System.out::println, throwable -> System.out.println(throwable.getMessage()));
sleep(1000);
disposable.dispose();
sleep(60000);
}
When I ran the code, I got the exception io.reactivex.exceptions.UndeliverableException as expected. As the discussion in above link, RxJava2 will not swallow the Throwable so we have to handle this. But when I tried to use the defer instead of fromCallable to create the Observable, surprised me, no error was fired.
Disposable disposable = Observable.defer(() -> Observable.just(simulateHeavyWork()))
It surprises me again when I use Flowable instead of Observable, the UndeliverableException happened again.
Disposable disposable = Flowable.defer(() -> Flowable.just(simulateHeavyWork()))
So I'm confusing about the difference between defer and fromCallable, Flowable and Observable in this case.

Managing thread schedulers when concat observables

I have a code like this:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.concatMap(getFullUserFromDto())
.subscribe(doSomehingWithUser());
private Func1<UserDto, Observable<User>> getFullUserFromDto() {
return new Func1<UserDto, Observable<User>>() {
#Override
public Observable<User> call(final UserDto dto) {
return dao.getUserById(dto.getUserId());
}
};
}
and in my DAO, I have:
public Observable<User> getUserById(final Long id) {
return api.getUserById(id).map(//more things...
}
Note there are two levels of "concatenation": service -> dao -> api. Method api.getUserById(id) make a network call.
I'm getting NetworkOnMainThreadException error. Why? I'm using and subscribeOn and observeOn operators, but it seems that it is not applied to the "final" built Observable.
If I use this operators in the API call, in the DAO, it works:
return api.getUserById(id)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(//more things...
Is there a way to use just once in the "root" Observable?
So, concatMap subscribes on Observables. What thread is used to perform this operation? Well, the thread that called onNext for the concatMat, given that it doesn't change threads/schedulers. So, one simple transposition should help with this:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.concatMap(getFullUserFromDto())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(doSomehingWithUser());
I'd also suggest to use Schedulers.io(), as it will re-use threads.
Short answer: use observeOn before chained operations to controll on which schedulers they are executed:
service.getUserById(10)
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.io())
.concatMap(getFullUserFromDto())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(doSomehingWithUser());
In the example above, .concatMap will be executed in Schedulers.io()
More details can be found here:
http://tomstechnicalblog.blogspot.com/2016/02/rxjava-understanding-observeon-and.html

Why is the second onSubscribe() call required in my observable sequence?

It is required that the onSubscribe() operator be applied a second time in my sequence of observables, see line: details.add(myApi.getDetails(h.getId()).subscribeOn(Schedulers.io()));. If the onSubscribe() operator is not applied, a NetworkOnMainThreadException is thrown.
My understanding is that since I'm already applying a subscribeOn(Schedulers.io()) operator early in the sequence, that all future subscriptions should happen on the on the io scheduler. What is wrong with my understanding? Is this potentially a retrofit beta2 issue since in the below example the myApi instance is created via Retrofit?
myApi.getHeadlines()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Headlines, Observable<HeadlineDetail> {
#Override
public Observable<HeadlineDetail> call(Headlines headlines) {
List<Observable<HeadlineDetail>> details = new ArrayList<>();
for (Headline h : headlines) {
details.add(myApi.getDetails(h.getId()).subscribeOn(Schedulers.io()));
}
return Observable.merge(details);
}
})
.subscribe(...);
Dependencies:
Retrofit Beta 2.0-beta2
Retrofit rxjava-adapter 2.0-beta2
rxjava v1.0.14
rxandroind v1.0.1
subscribeOn sets the thread the observable starts on, but observeOn affects the thread used for downstream operations. They are "observing" the original observable. You are starting on the background thread, but the switch everything to the main thread. Try moving your observeOn to later in your chain.
See the docs on observeOn for more detail.
myApi.getHeadlines()
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Headlines, Observable<HeadlineDetail> {
#Override
public Observable<HeadlineDetail> call(Headlines headlines) {
List<Observable<HeadlineDetail>> details = new ArrayList<>();
for (Headline h : headlines) {
details.add(myApi.getDetails(h.getId()));
}
return Observable.merge(details);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);

Categories

Resources