Kotlin协程与Retrofit2:HTTP请求完成后程序阻塞一分钟才退出的问题排查与解决咨询
Kotlin协程与Retrofit2:HTTP请求完成后程序阻塞一分钟才退出的问题排查与解决咨询
嘿,我来帮你分析这个问题并给出解决方案!
为什么程序不会立即退出?
问题出在OkHttp的线程池上——Retrofit底层依赖OkHttp处理HTTP请求,而OkHttp默认的Dispatcher(管理请求线程的核心组件)使用的是非守护线程。当你的HTTP请求完成后,这些线程不会立刻销毁,而是会保持空闲状态等待新请求,默认的空闲超时时间正好是60秒。
JVM的退出规则是:只有当所有非守护线程都终止时,JVM才会停止运行。所以哪怕你的runBlocking作用域已经执行完所有代码(打印完posts),OkHttp的空闲线程还在后台挂着,导致程序必须等满一分钟才会自动退出。
怎么让程序在请求完成后立刻退出?
这里有几个实用的解决办法,你可以根据场景选择:
方法1:自定义OkHttp客户端,缩短空闲超时时间
手动配置OkHttp的Dispatcher,把空闲线程的超时时间改短(比如1秒),这样线程会快速被回收,程序就能及时退出:
import okhttp3.OkHttpClient import retrofit2.http.GET import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import kotlinx.coroutines.* import java.util.concurrent.TimeUnit data class Post( val userId: Int, val id: Int, val title: String ) interface ApiService { @GET("posts") suspend fun getPosts(): List<Post> } object RetrofitClient { private const val BASE_URL = "https://jsonplaceholder.typicode.com/" // 自定义OkHttp客户端,设置Dispatcher的空闲超时 private val okHttpClient = OkHttpClient.Builder() .dispatcher( okhttp3.Dispatcher().apply { // 设置空闲线程超时为1秒 idleConnectionTimeout(1, TimeUnit.SECONDS) } ) .build() val instance: ApiService by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) // 替换默认的OkHttp客户端 .addConverterFactory(GsonConverterFactory.create()) .build() .create(ApiService::class.java) } } fun main() = runBlocking { val posts = RetrofitClient.instance.getPosts() posts.forEach { println(it) } }
方法2:手动关闭OkHttp的Dispatcher
在请求完成后,主动关闭OkHttp的Dispatcher,强制终止所有空闲线程:
fun main() = runBlocking { val posts = RetrofitClient.instance.getPosts() posts.forEach { println(it) } // 获取Retrofit关联的OkHttp客户端,关闭其线程池 val retrofit = RetrofitClient::class.java.getDeclaredField("instance").apply { isAccessible = true }.get(null) as Retrofit val okHttpClient = retrofit.callFactory() as OkHttpClient okHttpClient.dispatcher.executorService.shutdown() }
注:这种方式需要做类型转换,适合快速测试场景,正式项目建议用方法1。
方法3:让OkHttp使用守护线程
配置OkHttp的Dispatcher使用守护线程,这样JVM退出时不会等待这些线程:
private val okHttpClient = OkHttpClient.Builder() .dispatcher( okhttp3.Dispatcher(Executors.newCachedThreadPool { r -> Thread(r).apply { isDaemon = true } }) ) .build()
这种方式下,只要runBlocking的协程作用域执行完毕,JVM就会直接退出,无需等待OkHttp的空闲线程。
总结
核心原因就是OkHttp的非守护空闲线程阻止了JVM退出,上面的几种方法都能解决问题:
- 正式项目推荐用方法1,通过自定义OkHttp客户端调整超时时间,既规范又灵活;
- 测试脚本可以用方法3,设置守护线程最省心;
- 临时调试可以用方法2,手动关闭线程池快速见效。
内容来源于stack exchange
相关产品推荐
相关产品推荐

