Kotlin coroutines provide a concise and powerful way to handle asynchronous programming. RunBlocking is an important constructor in the coroutine library that allows us to run code within the coroutine and block the current thread until the coroutine is completed. Although the main purpose of coroutines is to handle asynchronous tasks non blocking, the original design intention of runBlocking is to connect the use of coroutines in existing asynchronous environments. This article will introduce the essence and usage scenarios of runBlocking.
RunBlocking is a coroutine constructor that starts a new coroutine and blocks the current thread until the coroutine completes execution. It is usually used to call suspended functions in a non coprogramming environment to ensure the execution order of code.
fun main() = runBlocking {
// Running code in coroutine
println("Hello, World!")
}The essence of runBlocking is to convert coroutine code blocks into blocking code for use in environments that do not support coroutines. This is very useful in scenarios where hanging functions need to be called from synchronized code. For example, call the Kotlin coroutine in Java code, or synchronously obtain the token in the Retrofit interceptor.
RunBlocking is suitable for scenarios where it is necessary to start a coroutine and block the current thread until it completes, such as obtaining tokens in a Retrofit interceptor and integrating with legacy block based code.
RunBlocking should be seen as a "bridge" tool used to connect the coroutine world and the non coroutine world, rather than the conventional usage pattern in pure coroutine code. There is essentially only one reasonable usage scenario for runBlocking in Android.
The only scenario is to act as a bridge between the coroutine world and the non coroutine world in a dedicated, fully controllable backend thread.
In some cases, runBlocking should not be used. Firstly, runBlocking should never be used directly in suspended functions. RunBlocking will block the current thread, and you should not make blocking calls in suspended functions.
// Strictly prohibit the use of runBlocking in suspended functions
suspend fun getToken() = runBlocking {
// ...
}We should also be careful not to use runBlocking on threads that should not be blocked. This is particularly a problem on Android, as blocking the main thread can cause ANR in the application.
// Error: Using runBlocking on the main thread
fun onClick() = runBlocking {
userNameView.test = getUserName()
}
// Correct: Use launch to start an asynchronous coroutine
fun onClick() {
lifecycleScope.launch {
userNameView.test = getUserName()
}
}Here is a specific example demonstrating how to use runBlocking in a Retrofit interceptor to synchronously obtain tokens.
1. Create the TokenProvider class
Firstly, we need a TokenProvider class to manage the acquisition and storage of tokens. This class will contain a hanging function getToken, which is used to retrieve tokens from the local database or network.
class TokenProvider(private val apiService: ApiService, private val tokenDao: TokenDao) {
// Suspend function, used to obtain Token
suspend fun getToken(): String {
// 尝试从本地数据库获取Token
var token = tokenDao.getToken()
// Attempt to retrieve tokens from the local database
if (token == null || token.isExpired()) {
token = fetchNewToken()
// Save the new token to the local database
tokenDao.saveToken(token)
}
return token.tokenValue
}
// Suspend function, used to obtain new tokens from the network
private suspend fun fetchNewToken(): Token {
val response = apiService.getNewToken()
if (response.isSuccessful) {
return response.body() ?: throw Exception("Token response body is null")
} else {
throw Exception("Failed to fetch new token: ${response.errorBody()?.string()}")
}
}
}2. Define ApiService interface
Next, we define a Retrofit interface ApiService for obtaining new tokens.
interface ApiService {
@POST("auth/token")
suspend fun getNewToken(): Response<Token>
}3. Define the TokenDao interface
We also need a DAO interface called TokenDao to access tokens in the local database.
interface TokenDao {
@Query("SELECT * FROM token LIMIT 1")
suspend fun getToken(): Token?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveToken(token: Token)
}4. Define Token data class
Define a data class Token, representing Token, including Token value and expiration time.
data class Token(
@PrimaryKey val id: Int,
val tokenValue: String,
val expiryDate: Long
) {
// Check if the Token has expired
fun isExpired(): Boolean {
return System.currentTimeMillis() > expiryDate
}
}5. Using runBlocking in the Interceptor
Finally, we use runBlocking in the Retrofit interceptor to synchronously obtain the token and add it to the request header.
class AuthInterceptor(private val tokenProvider: TokenProvider) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
// Use runBlocking to synchronize token acquisition
val token = runBlocking {
tokenProvider.getToken()
}
// Add token to request header
val newRequest = originalRequest.newBuilder()
.header("Authorization", token)
.build()
return chain.proceed(newRequest)
}
}In some cases, using runBlocking may be acceptable when working in a dedicated background thread (such as a HandlerThread). In these cases:
1.No blocking of critical threads: Operations are executed in a dedicated background thread.
2.Architectural constraints: A suspend function needs to be called in a callback-based system.
3.Sequential execution requirements: Tasks need to be completed in order.
class MessageWorkerThread @Inject constructor() {
private val handlerThread = HandlerThread(threadName).apply { start() }
private val handler: Handler = object : Handler(handlerThread.looper) {
override fun handleMessage(msg: Message) {
runBlocking {
checkSomething()
sendEmptyMessageDelayed(TASK_MESSAGE, 1000)
}
}
}
fun startChecking() {
Log.e("MessageWorkerThread", "start")
handler.sendEmptyMessage(TASK_MESSAGE)
}
suspend fun checkSomething() {
Log.e("MessageWorkerThread", "in:${Thread.currentThread().name}::checkSomething")
delay(1000)
}
companion object {
private const val TASK_MESSAGE: Int = 0x1001
private const val threadName: String = "MessageWorkerThread"
}
}Note: Even in this scenario, pure coroutine solutions should be prioritized, and `runBlocking` should only be used when architectural constraints are unavoidable.
Through in-depth analysis, we can conclude that in Android production code, there is indeed only one truly reasonable use case for `runBlocking`—as a bridge between different architectures within a dedicated background thread.
`runBlocking` should be considered a last resort, not a preferred tool. When considering using `runBlocking`, ask yourself three questions:
1. Do I know for sure that the current thread can be safely blocked?
2. Are there non-blocking alternatives?
3. Is this blocking operation within a fully controllable dedicated thread? Remember, in the world of Android coroutines, blocking is always the exception.








