Kotlin coroutines not running launch on main - kotlin

I have following code:
Timber.d("Calling coroutine from thread: ${Thread.currentThread().name}")
scope.launch {
Timber.d("Current thread: ${Thread.currentThread().name}")
runTest()
}
suspend fun runTest() {
coroutineScope {
launch(Dispatchers.Main) {
Timber.d("Running from thread: ${Thread.currentThread().name}")
}
}
}
If I run it. App crashes with no error in log.
In my log I see:
Calling coroutine from thread: main
Current thread: DefaultDispatcher-worker-2
But I don't see entry with Running from thread:
This is done in viewmodel
My scope looks like this:
val scope: ViewModelCoroutineScope = ViewModelCoroutineScope(Dispatchers.Default)
class ViewModelCoroutineScope(
context: CoroutineContext
) : CoroutineScope {
private var onViewDetachJob = Job()
override val coroutineContext: CoroutineContext = context + onViewDetachJob
fun onCleared() {
onViewDetachJob.cancel()
}
}
What am I doing wrong?

Moving to a different machine I finally got some errors about Dispatchers.MAIN not working.
In the end all I had to do was replace all raw coroutine dependencies with:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1")

Related

Why CoroutineExceptionHandler can only perform launch when the coroutineContext is MainScope()?

I have the below code and purposely trigger an exception to be caught by errorHandler
private var coroutineScope: CoroutineScope? = null
private val mainThreadSurrogate = newSingleThreadContext("Test Main")
#Before
fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
}
#After
fun tearDown() {
// reset main dispatcher to the original Main dispatcher
Dispatchers.resetMain()
mainThreadSurrogate.close()
}
private val errorHandler = CoroutineExceptionHandler { context, error ->
println("Launch Exception ${Thread.currentThread()}")
coroutineScope?.launch(Dispatchers.Main) {
println("Launch Exception Result ${Thread.currentThread()}")
}
}
#Test
fun testData() {
runBlocking {
coroutineScope = MainScope()
coroutineScope?.launch(errorHandler) {
println("Launch Fetch Started ${Thread.currentThread()}")
throw IllegalStateException("error")
}?.join()
}
}
This will result in
Launch Fetch Started Thread[Test Main #coroutine#2,5,main]
Launch Exception Thread[Test Main #coroutine#2,5,main]
Launch Exception Result Thread[Test Main #coroutine#3,5,main]
If I change coroutineScope = MainScope() to either
coroutineScope = CoroutineScope(Dispatchers.Main)
coroutineScope = CoroutineScope(Dispatchers.IO)
The coroutineScope?.launch(Dispatchers.Main) {...} will not run i.e. the Launch Exception Result ... will not get printed.
Why is it so?
Apparently, we need to create a Scope using SupervisorJob(), so that the parent job is not affected by the child job crash.
coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
Note the MainScope() is CoroutineScope(SupervisorJob() + Dispatchers.Main).
As mentioned in SupervisorJob
A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children, so a supervisor can implement a custom policy for handling failures of its children

Kotlin Coroutines: what's the diffrence between using NonCancellable and a standalone new Job

In Coroutines, when I want to guard a block of code against cancellation, I should add NonCancellable to the Context:
#Test
fun coroutineCancellation_NonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(NonCancellable) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
The above unit test will take ~1.1sec to execute, even though the long-running Coroutine is cancelled after just 100ms. That's the effect of NonCancellable and I understand this point.
However, the below code seems to be functionally equivalent:
#Test
fun coroutineCancellation_newJobInsteadOfNonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(Job()) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
I tried to find any functional differences between these two approaches in terms of cancellation, error handling and general functionality, but so far I found none. Currently, it looks like NonCancellable is in the framework just for readability.
Now, readability is important, so I'd prefer to use NonCancellable in code. However, its documentation makes it sound like it is, in fact, somehow different from a regular Job, so I want to understand this aspect in details.
So, my quesiton is: is there any functional difference between these two approaches (i.e. how can I modify these unit tests to have difference in outcomes)?
Edit:
Following Louis's answer I tested "making cleanup non-cancellable" scenario and in this case Job() also works analogous to NonCancellable. In the below example, unit test will run for more than 1sec, even though the coroutine is cancelled just after 200ms:
#Test
fun coroutineCancellation_jobInsteadOfNonCancellableInCleanup() {
runBlocking {
val scope = CoroutineScope(Job() + Dispatchers.Default + CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("test coroutine")) {
try {
delay(100)
throw java.lang.RuntimeException()
} catch (e: Exception) {
withContext(Job()) {
cleanup()
}
}
}
scope.launch {
delay(200)
launchJob.cancel()
}
launchJob.join()
}
}
private suspend fun cleanup() {
delay(1000)
}
NonCancellable doesn't respond to cancellation, while Job() does.
NonCancellable implements Job in a custom way, and it doesn't have the same behavior as Job() that is using cancellable implementation.
cancel() on NonCancellable is no-op, unlike for Job() where it would cancel any child coroutine, and where any crash in the child coroutines would propagate to that parent Job.

Launch gives a compliation error in Kotlin

I was trying out a few things in Kotlin. The launch in the following code gives a compilation error. However, the GlobalScope.launch works and giving launch inside runBlocking also work.
fun main() {
launch{
} }
If you see the definition of the launch:
fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job (source)
It is an extension function to the CoroutineScope, so it must be called on a CoroutineScope.
If you use the runBlocking, it'll give you a CoroutineScope as this variable in the block, so launch implicitly is this.launch.
In order to launch a coroutine it must have a lifecycle Job and a CoroutineDispatcher, which is contained in the CoroutineContext inside the CoroutineScope.
So if you want the coroutine, there is two common practise:
runBlocking (single-threaded start).
fun main() = runBlocking { // this: CoroutineScope
launch {} // implicit this.launch {}
}
coroutineScope factory function (starts at main thread but switches to default dispatcher when needed).
suspend fun main() = coroutineScope { // this: CoroutineScope
launch {} // implicit this.launch {}
}

function inside coroutine scope is not cancelled

Trying to grasp coroutines. I have an expectation that this code shouldn't print anything. However, it prints "work done" so cancellation didn't do a thing. How is it so?
suspend fun foo() = coroutineScope {
launch { doSomeWork() }
}
suspend fun doSomeWork() {
delay(10000)
println("Work done")
}
suspend fun main() {
val fooResult = foo()
fooResult.cancel()
}
I finally got it. The main coroutine suspends on "coroutineScope" call, that's why

How to launch a Kotlin coroutine in a `suspend fun` that uses the current parent Scope?

How can I launch a coroutine from a suspend function and have it use the current Scope? (so that the Scope doesn't end until the launched coroutine also ends)
I'd like to write something like the following –
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
go()
}
suspend fun go() {
launch {
println("go!")
}
}
But this has a syntax error: "Unresolved Reference: launch". It seems launch must be run in one of the following ways –
GlobalScope.launch {
println("Go!")
}
Or
runBlocking {
launch {
println("Go!")
}
}
Or
withContext(Dispatchers.Default) {
launch {
println("Go!")
}
}
Or
coroutineScope {
launch {
println("Go!")
}
}
None of these alternatives does what I need. Either the code "blocks" instead of "spawning", or it spawns but the parent scope won't wait for its completion before the parent scope itself ends.
I need it to "spawn" (launch) in the current parent coroutine scope, and that parent scope should wait for the spawned coroutine to finish before it ends itself.
I expected that a simple launch inside a suspend fun would be valid and use its parent scope.
I'm using Kotlin 1.3 and cotlinx-coroutines-core:1.0.1.
You should make the function go an extension function of CoroutineScope:
fun main() = runBlocking {
go()
go()
go()
println("End")
}
fun CoroutineScope.go() = launch {
println("go!")
}
Read this article to understand why it is not a good idea to start in a suspend functions other coroutines without creating a new coroutineScope{}.
The convention is: In a suspend functions call other suspend functions and create a new CoroutineScope, if you need to start parallel coroutines. The result is, that the coroutine will only return, when all newly started coroutines have finished (structured concurrency).
On the other side, if you need to start new coroutines without knowing the scope, You create an extensions function of CoroutineScope, which itself it not suspendable. Now the caller can decide which scope should be used.
I believe I found a solution, which is with(CoroutineScope(coroutineContext). The following example illustrates this –
import kotlinx.coroutines.*
fun main() = runBlocking {
go()
go()
go()
println("End")
}
suspend fun go() {
// GlobalScope.launch { // spawns, but doesn't use parent scope
// runBlocking { // blocks
// withContext(Dispatchers.Default) { // blocks
// coroutineScope { // blocks
with(CoroutineScope(coroutineContext)) { // spawns and uses parent scope!
launch {
delay(2000L)
println("Go!")
}
}
}
However, Rene posted a much better solution above.
Say you are dealing with some RxJava Observable and it isn't the time to refactor them, you can now get a hold of a suspend function's CoroutineScope this way:
suspend fun yourExtraordinarySuspendFunction() = coroutineScope {
val innerScope = this // i.e. coroutineScope
legacyRxJavaUggh.subscribe { somePayloadFromRxJava ->
innerScope.launch {
// TODO your extraordinary work
}
}
}