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()
Related
First question here, I will do my best.
I have a Data class that retrieve a data object with firestore at the creation.
I have done some code to the setters with coroutines. I am not sure of my solution but it is working. However, for the getters, I am struggling to wait the initialisation.
In the initialisation, I have a callback to retrieve the data. The issue that the callback is always called from the main thread, event if I use it in a coroutine in another thread. I check this with:
Log.d("THREAD", "Execution thread1: "+Thread.currentThread().name)
For the setter I use a coroutine in useTask to not block the main thread. And a mutex to block this coroutine until the initialisation in the init is done. Not sure about waitInitialisationSuspend but it is working.
But for the getter, I just want to block the main thread (even if it is bad design, it is a first solution) until the initialisation is done, and resume the getter to retrieve the value.
But I am not enable to block the main thread without also blocking the callback in the initialisation because there are in the same thread.
I have read many documentation about coroutine, scope, runBlocking, thread etc. but everything gets mixed up in my head.
class Story(val id: String) : BaseObservable() {
private val storyRef = StoryHelper.getStoryRef(id)!!
private var isInitialized = false
private val initMutex = Mutex(true)
#get:Bindable
var dbStory: DbStory? = null
init {
storyRef.get().addOnCompleteListener { task ->
if (task.isSuccessful && task.result != null) {
dbStory = snapshot.toObject(DbStory::class.java)!!
if (!isInitialized) {
initMutex.unlock()
isInitialized = true
}
notifyPropertyChanged(BR.dbStory)
}
}
}
fun interface StoryListener {
fun onEvent()
}
private fun useTask(function: (task: Task) -> Unit): Task {
val task = Task()
GlobalScope.launch {
waitInitialisationSuspend()
function(task)
}
return task
}
private suspend fun waitInitialisationSuspend()
{
initMutex.withLock {
// no op wait for unlock mutex
}
}
fun typicalSetFunction(value: String) : Task {
return useTask { task ->
storyRef.update("fieldName", value).addOnSuccessListener {
task.doEvent()
}
}
}
fun typicalGetFunction(): String
{
var result = ""
// want something to wait the callback in the init.
return result
}
}
RunBlocking seems to block the main tread, so I can not use it if the callback still use the main thread.
It is the same problem if I use a while loop in main thread.
#1
runBlocking {
initMutex.withLock {
result = dbStory!!.value
}
}
#2
while (!isInitialized){
}
result = dbStory!!.value
#3
Because maybe the callback in the init is in the main thread also. I have tried to launch this initialisation in a coroutines with a IO dispatcher but without success. The coroutine is well in a different thread but the callback still called in the main thread.
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope.launch() {
reference.get().addOnCompleteListener { task ->
In the getter, I have to work with the main thread. The solution is maybe to put the callback execution in another thread but I do not know how to do this. And maybe there is a better solution.
Another solution will be te be able to wait the callback in the main thread without blocking the callback but I have no solution for this.
Any ideas ?
I have loocked for many solutions and the conclusion is, don't do it.
This design is worse than I thougt. Android does not want you to block the main thread even for a short time. Blocking the main thread is blocking all UI and synchronisation mecanism, it is really bad solution.
Even using another thread for the callback (that you can do with an Executor) is, I think, a bad idea here. The good way to wait the end of the task in the callback is to retrieve the task and use:
Tasks.await(initTask)
But it is not allowed in the main thread. Android prevent you to do bad design here.
We should deal with the asynchronous way to manage firebase data base, it is the best way to do that.
I can still use my cache on the data. Here I was waiting to display a dialog with a text I retrieve in firebase. So, I can just display the dialog asynchronously when the text data is retrieved. If the cache is available, it will use it.
Keep also in mind that firebase seems to have some API to use a cache.
What is the best way to synchronize threads in this case:
fun doSomething() {
readFromDB.subscribe(object : DisposableMaybeObserver<List<Trip>>()) {
override onSuccess() {
callback.complete()
}
override onFailure() {
callback.complete()
}
}
}
Two threads access this block and run into a race condition.
I need only one thread to read from DB and have that state until the callback completes.
How to lock the other thread from executing this block.
Tried using a lock / synchronized. But, how to unlock from within the onSuccess or onFailure. Does not solve the problem.
In other words, how to wait for the thread to read from DB and get back onSuccess / onFailure, for the 2nd thread to do the same.
You could confine the observation to a single-threaded scheduler. Typically, one would need to run completion code on the main thread thus:
readFromDB
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : DisposableMaybeObserver<List<Trip>>()) {
override onSuccess() {
callback.complete()
}
override onFailure() {
callback.complete()
}
}
If the target thread doesn't matter, you could also use Schedulers.single().
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.
I know there are two methods available to do AsyncTask in Anko library.
doAsync()
doAsyncResult()
My question is both the above methods have onComplete() method. In both method's onComplete() there is no trace of result like AsyncTask.onPostExecute().
Example:
doAsync {
sdkServiceFactory.initSDKService()
onComplete { Log.d("Controller", "Sdk Connected") }
}
val result = doAsyncResult {
onComplete { Log.d("Controller", "Sdk Connected") }
sdkServiceFactory.initSDKService()
}.get()
In either method, I can get only the completed callback not the result. What are the similar methods available in Anko library for AsyncTask.onPreExecute() and AsyncTask.onPostExecute().
doAsync is used to execute code on a different thread, but does not return anything to the main thread when finished.
doAsyncResult is used to perform an activity on a separate thread, and the execute an operation on the main thread after completing execution on the separate thread.
To push anything to the main thread, add a new block with
uiThread {
//write you code here
}
in it.
Or better yet, create the method that you want to run asynchronously as a function with a return value. Then pass the method in to the doAsync call. To quote an example:
val longRunningTask: (AnkoAsyncContext<ListView>.() -> ArrayList<String>) = {
::doAnIntensiveActivity.invoke()
}
val f : Future<ArrayList<String>> = doAsyncResult(null, longRunningTask)