取消和超时
通过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