背景
看 coil
源码时,发现其内部有使用到 kotlin 的 runInterruptible
这个方法,参考 Android 关于 Coil 源码阅读之部分疑问记录 - 简书 (jianshu.com)。于是乎就在想这个方法到底有什么用,下面用实验来说明。
结论
先说结论,总的来说,就是当内部工作可能阻塞线程时,可以使用 runInterruptible
这个方法包裹工作,然后本次 job 被 cancel 时,可以有效的解除本线程的阻塞状态。大致流程是协程 cancel -> Thread#interrupt()
,进而解除阻塞状态。
实验
- 创建一个单线程的 CoroutineScope,一个协程里执行阻塞任务,另一个协程任务则无法执行。IDE 也智能地提示了 take 方法可能导致线程饥饿:
@OptIn(InternalCoroutinesApi::class)
class ExampleUnitTest {
@Test
fun main() {
runBlocking {
test()
}
// 确保上面代码执行完毕
Thread.sleep(3000L)
}
fun test() {
val context = ExperimentalCoroutineDispatcher(1, 1, 3000L, "single-thread")
val scope = CoroutineScope(context)
val queue = LinkedBlockingQueue<Int>(1)
val job = scope.launch {
try {
// runInterruptible {
println("阻塞前")
queue.take()
// }
} catch (e: CancellationException) {
println("eee: ${e.message}")
}
}
scope.launch {
println("阻塞后")
}
/*thread {
job.cancel("cancel xxx")
}*/
}
}
打印日志:
阻塞前
可以看到只打印了 阻塞前
而没有打印 阻塞后
,说明线程时被阻塞了的。
- 即时调用了
cancel
也无法解除阻塞状态
@OptIn(InternalCoroutinesApi::class)
class ExampleUnitTest {
@Test
fun main() {
runBlocking {
test()
}
// 确保上面代码执行完毕
Thread.sleep(3000L)
}
fun test() {
val context = ExperimentalCoroutineDispatcher(1, 1, 3000L, "single-thread")
val scope = CoroutineScope(context)
val queue = LinkedBlockingQueue<Int>(1)
val job = scope.launch {
try {
// runInterruptible {
println("阻塞前")
queue.take()
// }
} catch (e: CancellationException) {
println("eee: ${e.message}")
}
}
scope.launch {
println("阻塞后")
}
thread {
job.cancel("cancel xxx")
}
}
}
打印日志:
阻塞前
- 加上
runInterruptible
后,job 被 cancel 时可正常解除阻塞状态了
@OptIn(InternalCoroutinesApi::class)
class ExampleUnitTest {
@Test
fun main() {
runBlocking {
test()
}
// 确保上面代码执行完毕
Thread.sleep(3000L)
}
fun test() {
val context = ExperimentalCoroutineDispatcher(1, 1, 3000L, "single-thread")
val scope = CoroutineScope(context)
val queue = LinkedBlockingQueue<Int>(1)
val job = scope.launch {
try {
runInterruptible {
println("阻塞前")
queue.take()
}
} catch (e: CancellationException) {
println("eee: ${e.message}")
}
}
scope.launch {
println("阻塞后")
}
thread {
job.cancel("cancel xxx")
}
}
}
打印日志:
阻塞前
eee: cancel xxx
阻塞后
可以看到打印了 阻塞后
,证明线程确实解除了阻塞状态。
源码
最后,贴一下源码: