I have a problem with this block of code
val obs = PublishSubject.create<Int>()
obs
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe{ value ->
Log.i("TEST", "$value")
}
(1..4).forEach { obs.onNext(it) }
When I use subscribeOn mainThread and call onNext() nothing happens,nothing printed out.. It works well with subscribeOn Schedulers.io() or Schedulers.computation()
I dont know if its because im already in main thread and call subscribeOn(AndroidSchedulers.mainThread()) but when i try calling these block of code in some random worker thread then it works well
Related
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
I have the following code:
checkZipCode.exec(it)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
Timber.d("Main thread: ${Looper.getMainLooper().isCurrentThread}")
view.showLoading(true)
}
.doOnDispose { view.showLoading(false) }
.flatMapMaybe { isZipValid ->
if (isZipValid) {
userModel.zipCode = it.toString()
saveUser.exec(userModel)
autoSelectCityIfSingle.exec()
} else {
// TODO: Should show error here?
Maybe.empty()
}
}
.subscribe(...)
Logs are: Main thread: false
I thought that doOnSubscribe is called on the latest scheduler above it. Isn't it?
The subscription execution path isn't really an emission that observeOn acts upon. See
this question. If you really want to have something occur on the main thread in that spot in your chain, perhaps you can flatMap in another Observable that would do something within its doOnSubscribe (since that will occur on the emission thread, which is the main thread here).
Well, doOnSubscribe is executed on the same thread with subscribe().
I had problems because subscribe() was called on I/O thread.
Hi i am trying to implement a Single observable that chains two requests together.
In between the two requests i make, i notify a callback to update the UI with the response from request one and then launch the next request in the Schedulaers.io thread.
The issue i am having is that it tries to update the UI from the schedulars.io thread too and results to nothing being updated in the ui thread.
i cold wrap the calback on RunOnUiThread code block in android but wondering if there is a more elegant way of doing it?
i checked couroutines and it seems to just deal with putting a block of code in a seperate thread.
Here is my current code
override fun getHomeScreenInformation() {
delegator.requestOne()
.flatMap { responseOne->
homeScreenCallBack.onResponseOneRecieved(responseOne)
delegator.requestTwo()
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(
{responseTwo-> homeScreenCallBack.onResponseTwoRecieved(responseTwo)},
{error -> homeScreenCallBack.onError()}
)
}
Apply observeOn(AndroidSchedulers.mainThread()) as many times as necessary:
delegator.requestOne()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) // <----------------------
.flatMap { responseOne ->
homeScreenCallBack.onResponseOneRecieved(responseOne)
delegator.requestTwo()
.subscribeOn(Schedulers.io()) // <----------------------
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{responseTwo-> homeScreenCallBack.onResponseTwoRecieved(responseTwo)},
{error -> homeScreenCallBack.onError()}
)
I am dealing with the problem.
I am trying to call RxJava in the sync manner, however doing that results in blocking the Main thread.
Here is my code
#Override
public Single<SettingsBundle> getSettings() {
SettingsBundle settingsModel = mSettingsManager.getSettings();
return Single.just(settingsModel).map(mSettingsMapper);
}
And here is my sync call
#Override
public SettingsBundle getSettingsSync() {
return getSettings().blockingGet();
}
When calling the getSettingsSync the Main thread is blocked, however sometimes it works fine, what is more problematic.
I have tried something like that
#Override
public SettingsBundle getSettingsSync() {
return getSettings()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.blockingGet();
}
But it stills remains blocked.
What I am doing wrong, I would be grateful for any help.
Thanks.
TL;TR
never use observeOn(AndroidSchedulers.mainThread()) with blockingGet()
Long version
The output for:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val result =
Single.just("Hello")
.subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
.map {
println("1. blockingGet `$it` thread: ${Thread.currentThread()}")
return#map it
}
.blockingGet()
println("2. blockingGet `$result` thread: ${Thread.currentThread()}")
}
}
is
1. blockingGet `Hello` thread: Thread[RxCachedThreadScheduler-1,5,main]
2. blockingGet `Hello` thread: Thread[main,5,main]
As you can see result was generated on main thread (line 2), the map function was execute in the RxCachedThreadScheduler thread.
With the line .observeOn(AndroidSchedulers.mainThread()) decommented the blockingGet() never return and all is stucked.
.observeOn(AndroidSchedulers.mainThread())
.blockingGet();
The problem exists in this specific combination of operators. AndroidSchedulers schedules code to run on the main thread, however the blockingGet() stops more code from executing on that thread. Simply put AndroidSchedulers and the blocking operators of RxJava do not work well together.
Since the android scheduler might be used in the construction of the observable this means any use of the blocking* operators on the main thread will be prone to deadlocks regardless of what you try to do.
If you really need a function to run on the main thread and also need it to be synchronous, then you could do something like this:
If this is the main thread (Looper.myLooper() == Looper.getMainLooper()), then run func()
If not on the main thread, then you can use the combination of observeOn(AndroidSchedulers.mainThread()) with blockingGet()
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