https://kotlinlang.org logo
#coroutines
Title
# coroutines
s

Shawn Witte

03/31/2021, 9:25 PM
I have a remote call (
suspend
function via Retrofit) that is part of my (Android app's ) data syncing. I am attempting to ensure that the code block with this call completes, even if the calling coroutine (started in a
viewModelScope
) is cancelled. My initial thought was to wrap the function in
appScope.async { //code }.await()
, where
appScope
is an application-level scope. Android Studio has suggested that I just use
withContext(appScope.coroutineContext) { //code }
. Are these really equivalent in terms of coroutine lifecycles? I had thought that
withContext
created a child coroutine (with a different, but related
CoroutineContext
) that would be cancelled if the context creating it is cancelled, whereas
appScope.async()
creates a whole new coroutine with its own lifecycle tied to
appScope
and not the original context. Also welcoming input on my solution to the initial problem.
1
u

ursus

03/31/2021, 9:27 PM
depends on what you need, but usually there are objects which own the scope, and themselves are scoped via DI to app lifetime, or user session etc, .. so run the action there
s

Shawn Witte

03/31/2021, 9:55 PM
The question is if I have
Copy code
someScope.launch { 
    appScope.async {
        //code 
    }.await() 
}
and
Copy code
someScope.launch {
    withContext(appScope.coroutineContext) { 
        //code 
    } 
}
and I call
someScope.cancel()
, then does the inner work receive the cancellation signal in either case? My instinct is that it will in the second case but not the first case because
withContext
merges the contexts.
u

ursus

03/31/2021, 10:03 PM
don't know sorry, I usually structure it such that the wider DI scoped object exposes regular functions, and then uses scope internally, results are sideffected to some flow which is then exposed to public same as viewmodel has fooClick method and then exposes state: Flow; just a layer higher
s

Shawn Witte

03/31/2021, 10:31 PM
It appears that my instincts were right after testing the following on the Kotlin playground (
"did something1"
prints, but
"did something2"
does not):
Copy code
val appScope = CoroutineScope(Job() + Dispatchers.Default)
val tempScope = CoroutineScope(Job() + Dispatchers.Default)

runBlocking {
    tempScope.launch {
        val thing = appScope.async {
            delay(1000)
            println("did something1")
            "thing1"
        }.await()
        println(" did $thing")
    }

    tempScope.launch {
        val thing = withContext(appScope.coroutineContext) {
            delay(1000)
            println("did something2")
            "thing2"
        }
        println(" did $thing")
    }

    tempScope.cancel()
    delay(3000)

}
I also played with delays a bit because I was inconsistently getting
"did something2"
and
" did thing2"
, but I think that's because weird timings. It seems more consistent with a
delay
value of
2000
or more.
I take it back. The following gave me both
"did something1"
and
"did something2"
after
tempScope
was cancelled:
Copy code
runBlocking {
    val appScope = CoroutineScope(Job() + Dispatchers.Default)
    val tempScope = CoroutineScope(Job() + Dispatchers.Default)

    tempScope.launch {
        val thing = appScope.async {
            while (isActive) {
                delay(100)
                println("did something1")
            }
        }
        println(" did ${thing.await()}")
    }

    tempScope.launch {
        val thing = withContext(appScope.coroutineContext) {
            while (isActive) {
                delay(100)
                println("did something2")
            }
            "thing2"
        }
        println(" did $thing")
    }

    tempScope.cancel()
    delay(1000)
    println("tempScope cancelled")
    delay(1000)
    appScope.cancel()
    println("appScope cancelled")
}
n

natario1

04/01/2021, 7:58 AM
The timing of cancel() seems a bit flaky. I'd try canceling tempScope inside the async block, and use two separate runBlocking calls to compare the two options.
s

streetsofboston

04/01/2021, 6:44 PM
You'd want to switch CoroutineScopes, from the calling viewModelScope (that has its own lifecycle) and the one issuing the retrofit request (that has its own lifecycle). Some shameless self-promotion, here is an article i wrote about that (jump to the "Switching CoroutineScopes" section): https://medium.com/swlh/how-can-we-use-coroutinescopes-in-kotlin-2210695f0e89