How can CoroutineScope(job+Dispatchers.Main) run on the main/UI thread?
Asked Answered
M

2

9

If the operations inside CoroutineScope(job+Dispatchers.Main){...} run on the main thread then how come it does not violate Android's requirement that slow (blocking) operations (Networking etc.) are not allowed to run on the main/UI thread? I can run blocking operations with this scope and the UI does not freeze at all.

I would be grateful if someone could explain what is happening under the hood. My guess is that it is similar to how JavaScript manages blocking operations with the event loop, but I struggle to find any relevant materials.

Mills answered 18/2, 2020 at 3:56 Comment(2)
What do you mean by I can run blocking operations with this scope and the UI does not freeze at all. ? Please provide an example?Hollington
@DmitriiLeonov, for example, I can pause the thread by calling delay(1000). Shouldn't this cause an ANR screen to appear if I am doing this on the UI thread?Mills
R
5

My guess is that it is similar to how JavaScript manages blocking operations with the event loop

Yes, this is correct, the event loop is essential to making coroutines work. Basically, when you write this:

uiScope.launch {
    delay(1000)
    println("A second has passed")
}

it compiles into code that has the same effect as this:

Handler(Looper.mainLooper()).postDelayed(1000) { println("A second has passed") }

The main concept is the continuation, an object that implements a state machine that corresponds to the sequential code you wrote in a suspendable function. When you call delay or any other suspendable function, the continuation's entry-point method returns a special COROUTINE_SUSPENDED value. Later on, when some outside code comes up with the return value of the suspendable function, it must call continuation.resume(result). This call will be intercepted by the dispatcher in charge, which will post this call as an event on the GUI event loop. When the event handler is dequeued and executed, you are back inside the state machine which figures out where to resume the execution.

You can review this answer for a more fleshed-out example of using the Continuation API.

Rim answered 20/2, 2020 at 7:51 Comment(3)
Thanks for the explanation. I haven't seen anyone provide an equivalent Handler based example.Mills
The delay function is non-blocking. The OP was asking about a blocking operation, e.g. some image-processing logic.Tchao
@Tchao OP: "I can run blocking operations with this scope and the UI does not freeze at all." "for example, I can pause the thread by calling delay(1000)" This implies that they are using the word "blocking" in a more general sense than the usual jargon. In this sense, it includes suspending behavior as well.Rim
H
5

Running blocking operations and running suspending operations on CoroutineScope(Dispatchers.Main) are two different things.

delay() is a suspending function and it is non blocking

CoroutineScope(Dispatchers.Main){
    delay(6000)
}

While Thread.sleep() is blocking and calling code below will cause ANR

CoroutineScope(Dispatchers.Main){
    Thread.sleep(6000)
}

I suggest you checking Kotlin coroutines talk by Roman Elizarov on Kotlinconf 2017, especially the part where he runs 100,000 delay()

Hollington answered 19/2, 2020 at 3:6 Comment(1)
Thanks for the insights!Mills
R
5

My guess is that it is similar to how JavaScript manages blocking operations with the event loop

Yes, this is correct, the event loop is essential to making coroutines work. Basically, when you write this:

uiScope.launch {
    delay(1000)
    println("A second has passed")
}

it compiles into code that has the same effect as this:

Handler(Looper.mainLooper()).postDelayed(1000) { println("A second has passed") }

The main concept is the continuation, an object that implements a state machine that corresponds to the sequential code you wrote in a suspendable function. When you call delay or any other suspendable function, the continuation's entry-point method returns a special COROUTINE_SUSPENDED value. Later on, when some outside code comes up with the return value of the suspendable function, it must call continuation.resume(result). This call will be intercepted by the dispatcher in charge, which will post this call as an event on the GUI event loop. When the event handler is dequeued and executed, you are back inside the state machine which figures out where to resume the execution.

You can review this answer for a more fleshed-out example of using the Continuation API.

Rim answered 20/2, 2020 at 7:51 Comment(3)
Thanks for the explanation. I haven't seen anyone provide an equivalent Handler based example.Mills
The delay function is non-blocking. The OP was asking about a blocking operation, e.g. some image-processing logic.Tchao
@Tchao OP: "I can run blocking operations with this scope and the UI does not freeze at all." "for example, I can pause the thread by calling delay(1000)" This implies that they are using the word "blocking" in a more general sense than the usual jargon. In this sense, it includes suspending behavior as well.Rim

© 2022 - 2024 — McMap. All rights reserved.