Executing in Background in Java, Kotlin, Android
Table of contents
There are some recommendations about the methods chosen for executing a background task.
Ref: Background Task Overview, developer.android.
Besides methods decision, it is necessary to clear the difference between a thread and a coroutine. In one word, a thread can hold multiple coroutines, while a process can hold multiple threads.
Threads in Java
For single simple one, extend Thread and start it, or implement Runnable and put it in a thread and start.
For multiple threads, typically, create a ThreadPoolExecutor to manage multiple threads, or use its factory Executors to create one.
A Worker created in a thread pool is a Runnable , it is put into a thread when executing and a ReentrantLock is used to manage the workers. Specifically, the ReentrantLock is used to protect the manipulation of a HashSet<Worker> maintain in the thread pool.
Reference: Java线程池详解-线程池原理分析.
More readings:
JUC线程池:BlockingQueue详解 - 同步队列 SynchronousQueue, it is used in
Executors.newCachedThreadPool().JUC线程池:ScheduledThreadPoolExecutor详解 - 数据结构, about the
DelayedWorkQueueused inExecutors.newScheduledThreadPool().Package summary: java.util.concurrent, developer.android
Coroutines in Kotlin
Kotlin Features and Reference (https://developer.android.com/kotlin/coroutines)
- Suspension
- Structured concurrency with constrained coroutine scope, fewer memory leaks by automatically cancel
- Cancellation support
Coroutines execution are scheduled on multiple threads internally, internal implementation is sort of like a nice-wrapping executor service without bothering of creating and closing alike one. While Dispatchers provide some thread-level control before scheduling and executing a coroutine by specifying Default or IO, Main, guaranteeing some main-safe executing.
Basic usage
- use
CoroutineScope.launch{}
viewModelScope.launch(Dispatchers.IO) {
// some blocking operations
val jsonBody = “{xxx xxx}”
loginRepository.makeLoginRequest(jsonBody)
}
viewModelScope::a CoroutineScope launch::create a coroutine on Dispatchers.IO if specified, or else on UI thread Dispatchers.IO::indicator of IO thread (type)
- use
suspend functionandwithContext{}
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = “{xxx xxx}”
// Make the network call and suspend execution until it finishes
// suspend here
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
Readings
How does a Kotlin coroutine get to execute when using a suspend function, from a perspective of de-wrapping the simple form of coroutine? See here (图解Kotlin协程原理).
→ It is transformed in to a
Continuationusing CPS (continuation pass-style transform) by the compiler and executed as a state machine (aswitch-case).How to specified a thread for coroutines to run on? See here (Coroutines support: Force execution on certain thread).
→ Use
withContext(Handler.asCoroutineDispatcher()) { doXXX() }.How to make coroutines run on single thread? See here (Coroutines support: Coroutine dispatcher confined to a single thread).
→ Use
Executors.newSingleThreadExecutor().asCoroutineDispatcher().→ Also, ⚠️ do not use
runBlockinginside a coroutine, especially inside default or IO dispatcher, as it may lead to dead lock for the thread pool inside coroutines’ internal implementation. Specifically,runBlockingwould block the current thread executing the coroutine, which holds therunBlocking(SeerunBlockingsource code comment, and here: How I Fell in Kotlin’s RunBlocking Deadlock Trap, and How you can avoid it). For the case in above context, do something like this would be fine: (code snip from here)val singleDispatcher = Executors.newSigleThreadExecutor().asCoroutineDispatcher() // ... launch(Dispatchers.Default){ // do something multithreaded... val res = withContext(singleDispatcher){ // do somthething on a single thread... }//return to Default }A same opinion comes from this reading (How to Avoid the Kotlin Coroutine Deadlocks?).
The IO dispatcher has a default size of core threads with value 64. The default dispatcher has a default size of core threads with value of the number of CPU cores (available processors), having a least bound of 2.