Kotlin: lag in coroutine runBlocking - android

I am using kotlin Coroutines to perform async network operations to avoid NetworkOnMainThreadException.
The problem is the lag that happens when i use runBlocking,that take sometime to complete current thread.
How can i prevent this delay or lag,and allow the async operation to be done without delay
runBlocking {
val job = async (Dispatchers.IO) {
try{
//Network operations are here
}catch(){
}
}
}

By using runBlocking you are blocking the main thread until the coroutine finishes.
The NetworkOnMainThread exception is not thrown because technically the request is done on a background thread, but by making the main thread wait until the background thread is done, this is just as bad!
To fix this you could launch a coroutine, and any code that depends on the network request can be done inside the coroutine. This way code may still be executed on the main thread, but it never blocks.
// put this scope in your activity or fragment so you can cancel it in onDestroy()
val scope = MainScope()
// launch coroutine within scope
scope.launch(Dispachers.Main) {
try {
val result = withContext(Dispachters.IO) {
// do blocking networking on IO thread
""
}
// now back on the main thread and we can use 'result'. But it never blocked!
} catch(e: Exception) {
}
}
If you don't care about the result and just want to run some code on a different thread, this can be simplified to:
GlobalScope.launch(Dispatchers.IO) {
try {
// code on io thread
} catch(e: Exception) {
}
}
Note: if you are using variables or methods from the enclosing class you should still use your own scope so it can be cancelled in time.

Related

Explanation of intriguing behavior of coroutines

I have a method that runs some operations in a row. Is is actually a for loop, which loops it's contents 50 times, each iteration taking roughly 0.2 seconds. I have a constant animation being presented on the screen for the duration of this execution. So, it is obvious that I wish to carry these operations off the main thread, so my animation can keep up (or the recompositions can take place, this is Compose). What I realized is, that this simple method
fun run(){
repeat(10000) {
repeat(5000){
print("I ♥ Kotlin")
}
}
}
if run in a standard Composable scope just like that, will block the UI thread as one would expect.
b) it would also block the UI thread if I call it in a LaunchedEffect while nesting it in a call to launch{...}.
c) It does not block if I run it on an I/O coroutine, which is also the default coroutine.
d) the app sometimes crashes if run on the Main Dispatcher
Now, simple question - why is this?
LaunchedEffect(Unit){
run() // Block
}
Launchedeffect(Unit){
launch{
run() // Block
}
}
LaunchedEffect(Unit){
withContext(Dispatchers.Main){
run() //Blocks, and at times, crashes
}
}
LaunchedEffect(Unit){
withContext(Dispatchers.IO){
run() // Runs without blocking
}
}
thread{
run() //Runs without blocking, no crash
}
Can anyone explain why the Dispatchers.IO works and the others don't? It's sort of giving me undesired stress.
If anyone requires a quick animation UI to test it out, here it is
#Composable
fun DUM_E_MARK_II() {
val sizeTransition = rememberInfiniteTransition()
val size by sizeTransition.animateFloat(
initialValue = 50f,
targetValue = 200f,
animationSpec = infiniteRepeatable(
keyframes { durationMillis = 1000 },
repeatMode = RepeatMode.Reverse,
)
)
Icon(
imageVector = Icons.Filled.Warning,
contentDescription = "",
modifier = Modifier.size(size.dp),
tint = Color.Red
)
}
Your code is a long-running, non-suspendable task. It blocks whatever thread it runs on for its entire lifetime. When you block the UI thread, it causes the UI to freeze, and after a timeout Android kills such a misbehaving application.
If you use any dispatcher that uses its own thread pool, for example IO, the task will block a non-UI thread.
withContext(Dispatchers.IO){
run() // Runs without blocking
}
here, you're explicitly saying that you want to run this on another thread, specifically a thread which won't have an impact on the main thread, so when you call:
withContext(Dispatchers.Main){
run() //Blocks, and at times, crashes
}
then yes, this probably should crash with ANR exception, because the main thread has been blocked for too long, that's the point of withContext is to specify where this work should be done, and intensive or long running tasks should not be on Dispatchers.Main
This function uses dispatcher from the new context, shifting execution of the block into the different thread if a new dispatcher is specified, and back to the original dispatcher when it completes.
run() function is a long-running function, it will block the thread which executes it.
Let's consider each case one by one:
run() function is invoked in Main(UI) thread, blocking it.
LaunchedEffect(Unit) {
run() // Block
}
run() is invoked inside a coroutine, which is launched using launch coroutine builder. The context of the coroutine is the composition's CoroutineContext, I assume it consists of Dispatchers.Main dispatcher. So the run function is also invoked in the Main(UI) thread, blocking it.
Launchedeffect(Unit) {
launch {
run() // Block
}
}
You can make the run() function suspend using withContext(Dispatchers.IO), it will switch the execution context of the run function to Dispatchers.IO thread pool:
suspend fun run() = withContext(Dispatchers.IO) {
// this is executed in background thread
}
Launchedeffect(Unit) {
run() // Not Blocking
}
Launchedeffect(Unit) {
launch {
run() // Not Blocking
}
}
run() function is invoked in Main(UI) thread, blocking it, because Dispatchers.Main is used for its context execution. Dispatchers.Main executes a coroutine in the Main(UI) thread.
LaunchedEffect(Unit){
withContext(Dispatchers.Main){
run() // Blocks, and at times, crashes
}
}
In this case it runs without blocking because Dispatchers.IO is used as a coroutine context. It uses background pool of threads. It will not block the Main thread because it executes in background thread.
LaunchedEffect(Unit){
withContext(Dispatchers.IO){
run() // Runs without blocking
}
}
This runs without blocking the Main thread because another thread (background thread) is used to execute it.
thread{
run() //Runs without blocking, no crash
}

How to use a callback in specific thread in order to wait for it in the main thread?

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.

Android multithreading - coroutine and UI thread

I am new to multithreading and looking for solution for this problem.
I am launching a method in coroutine which updates data in my database and if it is updated I would like to update the UI for users. How to this? I cannot put runOnUiThread inside a coroutine. Is there some type of magic like -> when coroutine finished -> then -> runOnUi?
Greetings
You don't need to call runOnUiThread as the coroutine will have the main dispatcher as the context.
Let's say you have this helper function to offload work to the I/O thread.
suspend fun <T> withIO(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block)
If you are using a ViewModel, then you can call it like this
viewModelScope.launch {
val result = withIO {
// You are on IO thread here.
update your database
}
// The block will be suspended until the above task is done.
// You are on UI thread now.
// Update your UI.
}
If you are not using a ViewModel, you can also use
withContext(Disptachers.Main) {
val result = withIO {
// You are on IO thread
}
// You are back on the main thread with the result from the task
}
Coroutine are task that work on different thread.
What you really want is wating for changes in database. Coroutine in this idea could work for insert data in db, but listening part is role of ViewModel pattern.
I recently answer similar question to yours:
AutocompleteTextView with room
More specific could be this answer from another user:
Wait until Kotlin coroutine finishes in onCreateView()
So the basic problem is to jumping back to main thread after co-routine finishes
this can be done multiple ways
using launch(Dispatcher.Main)
from main thread init co-routine
something like this
//launches coroutine running on main thread
GlobalScope.launch(Dispatchers.Main) {
updateDb()
}
suspend fun updateDb(){
//runs on worker thread and returns data
val value = withContext(Dispatchers.IO){
saveDataInDb();
}
//runs back on main thread
updateUI(value);
}
However global scope should not be used
You can read about that here https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc
using async await
suspend fun saveInDb() {
val value = GlobalScope.async {
delay(1000)
println("thread running on [${Thread.currentThread().name}]")
10
}
println("value = ${value.await()} thread running on [${Thread.currentThread().name}]")
}
output:
thread running on [DefaultDispatcher-worker-1]
value = 10 thread running on [main]
thread running on [main]

Switching to UI context in coroutines

I'm new to coroutines and I'm wondering if it's possible to switch from coroutineScope (GlobalScope) to UI scope for the code below. My problem is that the steps inside the coroutine launch body must be executed in a worker thread, otherwise the listener notification must be executed in the ui thread in order to avoid to call runOnUiThread in my activity code.
override suspend fun startRent(name: String, bikeMode: BikeMode, listener: StartRentListener) {
var bleDevice : RxBleDevice
val scanFilter: ScanFilter = ScanFilter.Builder().setDeviceName(name).build()
val scanSettings: ScanSettings = ScanSettings.Builder().build()
val job = GlobalScope.launch {
try {
bleDevice = rxBleClient.scanBleDevicesExt(rxBleClient, scanSettings, scanFilter)
val bleConnection = bleDevice.establishConnectionExt()
// write handshake
connectionManager.writeHandshake(bleDevice, bleConnection)
// open lock
openLock(bleDevice, bikeMode, bleConnection)
// getting user position
apiHelper.sendLockRequest(bleDevice.name, getPosition())
bleDevice.disconnect()
// this should be called on main thread once all the previous operations are finished
listener.onSuccess()
} catch (e: Exception) {
listener.onError(e)
}
}
job.join()
}
A snippet of my current activity code:
bikeAccessClient.startRent(bikeBLEName, BikeMode.HYBRID, object :
StartRentListener {
override fun onSuccess() {
runOnUiThread {
// UI update here
}
}
You may use withContext(Dispatchers.Main) {..} function to execute a part of your code with the other Coroutine Dispatcher.
kotlinx.coroutines.android contains the definition of the Dispatchers.Main function and it integrates correctly with Android UI.
Using explicit Dispatcher in your code is quite error-prone. Instead, I would recommend designing the code with fewer explicit requirements.
I would wrote something like that:
fun uiActionHandlerToStartTheProcess() {
launch(Dispatchers.Main) {
val result = startRent(...) // no callback here, suspend function
//UI Update Here
}
}
suspend fun CoroutineScope.startRent() : SomeResultOfWork {
//that function offloads the execution to a IO (aka brackground) thread
return withContext(Dispatchers.IO){
//here goes your code from `startRent`
//use `suspendCancellableCoroutine {cont -> .. }` if you need to handle callbacks from it
SomeResultOfWork()
}
The code in the launch(Dispatchers.Main){..} block is executed in the UI thread. The call to startRent suspend function suspends the execution in the UI thread. Once the startRent is ready with the reply (from a background thread) it resumes the execution (which is done by the Dispatchers.Main and equivalent to the runOnUiThread {...}) and executes the UI update from the right thread

Kotlin Coroutines - Are nested coroutines the proper way to handle different threading within one coroutine?

I'm trying out coroutines instead of RxJava on basic network calls for the fist time to see what it's like and running into some issues with lag/threading
In the below code, I'm doing a network call userRepo.Login() and if an exception happens I show an error message and stop the progress animation that I started at the start of the function.
If I leave everything on the CommonPool (or don't add any pool) it crashes saying the animation must be done on a looper thread if an exception happens. In other circumstances I've received errors saying this must be done on the UI thread as well, same problem, different thread requirements.
I can't launch the whole coroutine on the UI thread though, because the login call will block since it's on the UI thread and messes up my animation (which makes sense).
The only way I can see to resolve this, is the launch a new coroutine on the UI thread from within the existing coroutine, which works, but seems weird.
Is this the proper way to do things, or am I missing something?
override fun loginButtonPressed(email: String, password: String) {
view.showSignInProgressAnimation()
launch(CommonPool) {
try {
val user = userRepo.login(email, password)
if (user != null) {
view.launchMainActivity()
}
} catch (exception: AuthException) {
launch(UI) {
view.showErrorMessage(exception.message, exception.code)
view.stopSignInProgressAnimation()
}
}
}
}
You should start from the opposite end: launch a UI-based coroutine, from which you hand off heavy operations to an external pool. The tool of choice is withContext():
override fun loginButtonPressed(email: String, password: String) {
view.showSignInProgressAnimation()
// assuming `this` is a CoroutineScope with dispatcher = Main...
this.launch {
try {
val user = withContext(IO) {
userRepo.login(email, password)
}
if (user != null) {
view.launchMainActivity()
}
} catch (exception: AuthException) {
view.showErrorMessage(exception.message, exception.code)
view.stopSignInProgressAnimation()
}
}
}
This way you keep your natural Android programming model, which assumes the GUI thread.

Categories

Resources