When to use withContext? - android

Currently my code looks like this i have a ViewModel that calls the repository to do some background computations and return a result.
ViewModel function is run with viewModelScope.launch(Dispatchers.IO) and then the repository one is a suspend function.
Do I have to use return withContext{} to ensure that everything will be done sequentially? I checked and it is indeed sequential, but in the documentation i found that it doesn't have to be?

Let's dissect the viewModelScope first from it's source code. It uses a custom implementation of CouroutineScope. The context comprises of a SupervisorJob and a Dispatchers.Main dispatcher. This ensures that the coroutine is launched on the main thread and it's failure doesn't affect other coroutines in the scope.
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
Couple of examples worth exploring.
viewModelScope.launch {
Log.d("ViewModel", "Just viewModelScope: ${Thread.currentThread().name}")
}
// Output: Just viewModelScope: main
viewModelScope.launch(Dispatchers.IO) {
Log.d("ViewModel", "IO viewModelScope: ${Thread.currentThread().name}")
}
// Output: IO viewModelScope: DefaultDispatcher-worker-3
viewModelScope.launch {
Log.d("ViewModel", "viewModelScope thread: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
delay(3000)
Log.d("ViewModel", "withContext thread: ${Thread.currentThread().name}")
}
Log.d("ViewModel", "I'm finished!")
}
// Output:
// viewModelScope thread: main
// withContext thread: DefaultDispatcher-worker-4
I checked and it is indeed sequential, but in the documentation i
found that it doesn't have to be.
withContext() is a suspending operation and the coroutine will suspend till it's completion and then proceed ahead. That is apparent from the third example above.
In summary, viewModelScope will use main thread to execute a coroutine whose cancellation won't affect other couroutines. Use withContext when you want to do heavy task off of main thread in a suspending fashion; dispatch it using an appropriate dispatcher. Kotlin Coroutine guide is worth a read.
Edit:
Consider the below code as a single unit of execution. This illustrates the fact that when using withContext(), the caller thread is suspending, but it is not blocked, which allows it to go ahead and pick up some other pending work. The interleaving of the output loggers is of interest to us.
viewModelScope.launch {
Log.d("ViewModel", "viewModelScope thread: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
delay(3000)
Log.d("ViewModel", "withContext thread: ${Thread.currentThread().name}")
}
Log.d("ViewModel", "I'm finished!")
}
viewModelScope.launch {
Log.d("ViewModel", "I'm not blocked: ${Thread.currentThread().name}")
}
// Output:
// viewModelScope thread: main
// I'm not blocked: main
// withContext thread: DefaultDispatcher-worker-2
// I'm finished!

Related

Android Kotlin async/coroutines usage

I have a function that runs on the Android main thread (UI thread):
fun someFunc() {
...
doSomething1()
doSomething2()
doSomething3()
...
}
I want to run doSomething2() asynchronously in a background thread and make sure that someFunc() is suspended (not blocked) until this execution completes. Once done, someFunc() should resume in the main thread (from doSomething3()). During this background thread execution, I want to make sure that main thread is free and not blocked.
I know this can be done using Futures, but I'm wondering how to do this using coroutines/async?
You can call those functions in a coroutine, launched using viewModelScope in a ViewModel class or lifecycleScope in Activity/Fragment:
fun someFunc() = viewModelScope.launch {
doSomething1()
doSomething2()
doSomething3()
}
suspend fun doSomething2() = withContext(Dispatchers.IO) {
...
}
To run doSomething2() in a background thread we need to switch context of the coroutine to Dispatchers.IO using withContext() function.

Coroutines - Dispatchers.Main.immediate with join is deadlocking inside runBlocking

Breaking down a simple case on Android to suspend the main thread and perform concurrent processing with coroutines, the following code only prints Launched and runBlocking never completes:
runBlocking {
val ioJob = launch(Dispatchers.IO) {
delay(1000)
}
val mainJob = launch(Dispatchers.Main.immediate) {
Log.d("Routine", "Launched")
ioJob.join()
Log.d("Routine", "Joined")
}
listOf(mainJob, ioJob).joinAll()
}
Of course if we replace Dispatchers.Main.immediate with Dispatchers.IO everything works great, but some of my concurrent processing should be run on main. Using Dispatchers.Main doesn't log anything, as expected. It appears that once a join is performed within the root of runBlocking it paralyzes anything suspended that was dispatched to main.
Worth noting, an approach predicated on CountDownLatch and threads works great:
val latchMain = CountDownLatch(1)
val primaryLatch = CountDownLatch(2)
val ioExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory())
log("Execute IO")
ioExecutor.execute(
Runnable {
log("Delay IO")
Thread.sleep(1000)
log("Countdown Main")
latchMain.countDown()
Thread.sleep(3000)
primaryLatch.countDown()
})
log("Execute Main")
Runnable {
log("Await Main")
latchMain.await()
log("Countdown Primary")
primaryLatch.countDown()
}.run()
log("Await Primary")
primaryLatch.await()
log("Continue")
stepTracker.endTracking()
return stepTracker.stepGraphTrace
}
private fun log(msg: String) = Log.i("Routine", "[${Thread.currentThread().name}] $msg")
With output:
2021-08-11 11:04:06.508 [main] Execute IO
2021-08-11 11:04:06.509 [main] Execute Main
2021-08-11 11:04:06.510 [main] Await Main
2021-08-11 11:04:06.510 [pool-25-thread-1] Delay IO
2021-08-11 11:04:07.512 [pool-25-thread-1] Countdown Main
2021-08-11 11:04:07.513 [main] Countdown Primary
2021-08-11 11:04:07.513 [main] Await Primary
2021-08-11 11:04:10.514 [main] Continue
Any ideas? About to go submit an issue to JetBrains on this one.
Note: To clear things out, one should not deliberately block the main/UI thread with runBlocking, because while the UI thread get's released inside runBlocking (if it suspends) for its child coroutines, nothing outside the runBlocking gets executed (no draw methods, nothing), which leads to frozen UI for as long as runBlocking is active.
It's probably due to how "immediate" is implemented. It's not just join(), it's
any suspend function, if you call yield() it won't help, and if you call delay()
mainJob will resume only when the delay is done. Basically mainJob will not
resume as long as runBlocking is running, and runBlocking will not finish until
mainJob is done, which is a deadlock by definition.
You can omit specifying Dispatcher.Main.immediate to the mainJob and let it
inherit its context from runBlocking. And if you want to start executing mainJob as soon
as its declared just yield the thread from runBlocking to it.
runBlocking {
log("runBlocking: start")
val ioJob = launch(Dispatchers.IO) {
log("ioJob: About to delay")
delay(1000)
log("ioJob: Delay done")
}
val mainJob = launch {
log("mainJob: About to join ioJob")
ioJob.join()
log("mainJob: ioJob successfully joined")
}
// yield() if you want to start executing mainJob before the rest of the runBlocking code
log("runBlocking: about to join jobs")
listOf(ioJob, mainJob).joinAll()
log("runBlocking: jobs joined, exiting")
}
private fun log(msg: String) = Log.i("MainActivity", "[${Thread.currentThread().name}] $msg")
w/o yield()
I/MainActivity: [main] runBlocking: start
I/MainActivity: [main] runBlocking: about to join jobs
I/MainActivity: [DefaultDispatcher-worker-1] ioJob: About to delay
I/MainActivity: [main] mainJob: About to join ioJob
I/MainActivity: [DefaultDispatcher-worker-3] ioJob: Delay done
I/MainActivity: [main] mainJob: ioJob successfully joined
I/MainActivity: [main] runBlocking: jobs joined, exiting
Assuming this runBlocking code is called from the main thread, then the inner coroutine launched on Dispatchers.Main will never reach the front of the queue on the main thread looper because runBlocking is still blocking the main thread, so the coroutine can never start.
When you use Main.immediate, the beginning of the launched coroutine can at least run because immediate runs the coroutine up to the first suspension point if you're already on the main thread, which you are if you launched runBlocking on the main thread. When it reaches the suspend function call join(), it gets put in the main thread looper's queue and you're back to the same problem as above. Suspend function calls always break up the continuation of the coroutine.
Not answering the original question but questioning the question itself ;-).
The latch based solution "pretends" to use concurrency for both "jobs" but only the io job does. The second Runnable is just wrapping the code to run and does nothing.
This is doing the exact same thing but simpler:
val latchMain = CountDownLatch(1)
val ioExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory())
ioExecutor.execute(
Runnable {
Thread.sleep(1000)
latchMain.countDown()
}
)
latchMain.await()
The "identical" coroutine based implementation is very simple:
runBlocking {
val ioJob = launch(Dispatchers.IO) {
delay(1000)
}
ioJob.join()
}
Or using the same thread pool for the io job:
runBlocking {
val ioJob = launch(ioExecutor.asCoroutineDispatcher()) {
delay(1000)
}
ioJob.join()
}

Android: kotlin coroutine background task

I have a method
fun refrehList() {
viewModelScope.launch {
myData.value = withContext(Dispatchers.Default) {
summaryRepository.getSummaries(true)
}
allData.value = withContext(Dispatchers.Default) {
SummaryRepository.getSummaries(false)
}
}
}
Is this correct way of using coroutine. Is the DB operation happening in the background scope
If you're using Room, its documentation states the following:
You can add the suspend Kotlin keyword to your DAO methods to make
them asynchronous using Kotlin coroutines functionality. This ensures
that they cannot be executed on the main thread.
So you will be safe calling your repository inside the viewModelScope without changing context.
You can find that Room's documentation section here.
Yes this code will run on separate thread but one after another. Also you should be using Dispatchers.IO for database calls instead of Dispatchers.Default See Io vs Default.
viewModelScope.launch {
myData.value = withContext(Dispatchers.IO) {
Log.e("thread1", Thread.currentThread().name)
summaryRepository.getSummaries(true)
}
Log.e("thread2", Thread.currentThread().name)
allData.value = withContext(Dispatchers.IO) {
Log.e("thread3", Thread.currentThread().name)
SummaryRepository.getSummaries(false)
}
}
This will print something like :-
E/thread: DefaultDispatcher-worker-1
E/thread2: main
E/thread3: DefaultDispatcher-worker-1
If you want to run those co-routine in parallel you can use async-await .

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

Categories

Resources