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
DelayedWorkQueue
used 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 function
andwithContext{}
// 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
Continuation
using 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
runBlocking
inside a coroutine, especially inside default or IO dispatcher, as it may lead to dead lock for the thread pool inside coroutines’ internal implementation. Specifically,runBlocking
would block the current thread executing the coroutine, which holds therunBlocking
(SeerunBlocking
source 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.