Skip to Content
KotlinCoroutine取消和超时

取消和超时

通过launch返回的Job,可以对所创建的协程进行操作

fun main() = runBlocking { val job = launch { repeat(1000) { println("coroutine: I'm sleeping $it") delay(500L) } } delay(1300L) println("I'm tried to waiting") job.cancel() // 取消协程 job.join() // 等待协程运行完毕 println("I'm quiting") }

一旦main中调用了cancel,协程将会被取消,可以使用cancelAndJoin来代替cancel+join

取消是合作性的

协程的取消是合作性的,协程所有的代码必须配合才能取消,协程中所有的挂起函数都是可以被取消的,它们将检查协程的取消情况,并在取消时抛出CancellationException异常。但如果协程在计算中,且不检查取消,在其内部无可被取消的函数时,它将无法被取消

fun main() = runBlocking { val job = launch(Dispatchers.Default) { println("coroutine: I'm sleeping") waitForSeconds(10) println("coroutine: I'm wake") } delay(1300L) println("main:I'm waiting") job.cancelAndJoin() println("main: I'm exiting") } fun waitForSeconds(value: Long) { val previousTime = System.currentTimeMillis() while (System.currentTimeMillis() < previousTime + (value * 1000)); }

在这段代码中,即使协程被取消了,但是它仍然会继续运行

使用try..catch捕获这个异常也会有这种效果

fun main() = runBlocking { val job = launch(Dispatchers.Default) { repeat(5) { println("coroutine: $it") try { delay(1000L) } catch (e: CancellationException) { println(e) } } } delay(1500L) println("main is waiting...") job.cancelAndJoin() println("main is exiting...") }

但这种try..catch是一种反模式

使计算代码可以被取消

可以通过yeild函数或自行判断使得这种计算代码能够随着协程的取消而结束

while (isActive) { if (System.currentTimeMillis() >= previousTime) { println("coroutine: I'm sleeping") previousTime+=2000L } }

使用finally释放资源

因为中断函数在被取消时将会抛出异常,所以可以通过try..catch或者use的方式释放资源

val job = launch(Dispatchers.Default) { try { repeat(1000) { println("coroutine is sleeping... $it") delay(500L) } } finally { println("coroutine is running finally") } }

运行不可取消的块

使用withContext(NonCancellable)的方式,能够在已被取消的协程中仍然挂起

val job = launch { try { repeat(1000) { println("coroutine is sleeping... $it") delay(500L) } } finally { withContext(NonCancellable) { println("I'm running finally") delay(10000L) println("coroutine is finished...") } } }

超时

可以使用withTimeout为一个协程设置超时时间,在协程运行时间超时时,将会抛出TimeoutCancellationException异常

同样的,可以使用try..catch来处理资源释放,或者使用withTimeoutOrNull来默认返回一个空值

val result = withTimeoutOrNull(1000L) { delay(1200L) } println(result)

异步超时和资源

withTimeout中的超时事件相对于其块中运行的代码是异步的,并且可能随时发生,在块内申请的资源需要在块外关闭或者释放

如果在withTimeout中触发了超时,而资源没有关闭,可能会导致资源泄漏。这时,一般将释放资源的方法写在withTimeout块外

var acquired = 0 class MyResource : Closeable { init { acquired++ } override fun close() { acquired-- } } fun main() { runBlocking { repeat(10_000) { launch { var resource: MyResource? = null try { withTimeout(60) { delay(50) resource = MyResource() } } finally { resource?.close() } } } } println(acquired) }

值得注意的是,此处的acquired++acquired--因为在同一个runBlocking块中,它们是线程安全的

Last updated on