I am quite used to using RX to handle concurrency, but, in my current job, we have a mix of AsyncTask, Executors + Handlers, Threads and some LiveData thrown in. Now we are thinking about moving towards using Kotlin Coroutines (and in fact have started using it in certain places in the codebase).
Therefore, I need to start wrapping my head around Coroutines, ideally drawing from my existing knowledge of concurrency tools to speed the process up.
I have tried following the Google codelab for them and whilst it's giving me a bit of understanding it's also raising lots of unanswered questions so I've tried getting my hands dirty by writing some code, debugging and looking at log outputs.
As I understand it, a coroutine is composed of 2 main building blocks; suspend functions which are where you do your work and coroutine contexts which is where you execute suspend functions such that you can have a handle on what dispatchers the coroutines will run on.
Here I have some code below, that behaves as I would expect. I have set up a coroutine context using Dispatchers.Main. So, as expected, when I launch the coroutine getResources it ends up blocking the UI thread for 5 seconds due to the Thread.sleep(5000):
private const val TAG = "Coroutines"
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
log("onCreate", "launching coroutine")
launch {
val resource = getResource()
log("onCreate", "resource fetched: $resource")
findViewById<TextView>(R.id.textView).text = resource.toString()
}
log("onCreate", "coroutine launched")
}
private suspend fun getResource() : Int {
log("getResource", "about to sleep for 5000ms")
Thread.sleep(5000)
log("getResource", "finished fetching resource")
return 1
}
private fun log(methodName: String, toLog: String) {
Log.d(TAG,"$methodName: $toLog: ${Thread.currentThread().name}")
}
}
When I run this code, I see the following logs:
2020-05-28 11:42:44.364 9819-9819/? D/Coroutines: onCreate: launching coroutine: main
2020-05-28 11:42:44.376 9819-9819/? D/Coroutines: onCreate: coroutine launched: main
2020-05-28 11:42:44.469 9819-9819/? D/Coroutines: getResource: about to sleep for 5000ms: main
2020-05-28 11:42:49.471 9819-9819/com.example.coroutines D/Coroutines: getResource: finished fetching resource: main
2020-05-28 11:42:49.472 9819-9819/com.example.coroutines D/Coroutines: onCreate: resource fetched: 1: main
As you can see, all the logs originated from the main thread, and there is a 5 second gap between the log before and after the Thread.sleep(5000). During that 5 second gap, the UI thread is blocked, I can confirm this by just looking at the emulator; it doens't render any UI because onCreate is blocked.
Now, if I update the getResources function to use the suspend fun delay(5000) instead of using Thread.sleep(5000) like so:
private suspend fun getResource() : Int {
log("getResource", "about to sleep for 5000ms")
delay(5000)
log("getResource", "finished fetching resource")
return 1
}
Then what I end up seeing confuses me. I understand delay isn't the same as Thread.sleep, but because I am running it within the coroutine context which is backed by Dispatchers.Main, I expected to see the same result as using Thread.sleep.
Instead, what I see is the UI thread is not blocked while the 5 second delay is happening, and the logs look like:
2020-05-28 11:54:19.099 10038-10038/com.example.coroutines D/Coroutines: onCreate: launching coroutine: main
2020-05-28 11:54:19.111 10038-10038/com.example.coroutines D/Coroutines: onCreate: coroutine launched: main
2020-05-28 11:54:19.152 10038-10038/com.example.coroutines D/Coroutines: getResource: about to sleep for 5000ms: main
2020-05-28 11:54:24.167 10038-10038/com.example.coroutines D/Coroutines: getResource: finished fetching resource: main
2020-05-28 11:54:24.168 10038-10038/com.example.coroutines D/Coroutines: onCreate: resource fetched: 1: main
I can see the UI thread is not blocked in this case as the UI renders whilst the delay is taking place and then the text view is updated after 5 seconds.
So, my question is, how does delay, in this case, not block the UI thread (even though the logs in my suspend function still indicate that the function is running on the main thread...)