跳至主要內容

超时取消

guodongAndroid大约 2 分钟

长时间运行的协程有时需要在经过一段时间后终止。虽然你可以手动持有对相应 Job 的引用,并在延迟后启动单独的协程来取消持有的协程,但是协程库提供了一个便利的函数 —— withTimeout。在 starter 项目中打开 WithTimeoutExample.kt 文件并编写以下代码:

fun main() = runBlocking {
	withTimeout(1500L) {
		repeat(1000) { i ->
			println("$i. Crunching numbers [Beep.Boop.Beep]...")
			delay(500L)
		}
	}
}

输出如下:

0. Crunching numbers [Beep.Boop.Beep]...
1. Crunching numbers [Beep.Boop.Beep]...
2. Crunching numbers [Beep.Boop.Beep]...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1500 ms
	...

withTimeout 抛出的 TimeoutCancellationExceptionCancellationException 的子类。你以前从未在控制台上看到过它的堆栈跟踪。那是因为,在取消的协程中,CancellationException 是被认为协程完成的正常原因。然而,在这个示例中你在 main 函数中使用了 withTimeout 函数。

因为取消只是一个异常,所以你可以以通常的方式关闭所有资源。如果你需要一些额外的操作,你可以将超时的代码包装在标准的 try/catch 块中。打开 TimeoutCancellationExceptionHandling.kt 并基于上面示例的代码添加 try/catch 块,如下所示:

fun main() = runBlocking {
	try {
		withTimeout(1500L) {
			repeat(1000) { i ->
				println("$i. Crunching numbers [Beep.Boop.Beep]...")
				delay(500L)
            }
		}
	} catch (e: TimeoutCancellationException) {
		println("Caught ${e.javaClass.simpleName}")
	}
}

输出如下:

0. Crunching numbers [Beep.Boop.Beep]...
1. Crunching numbers [Beep.Boop.Beep]...
2. Crunching numbers [Beep.Boop.Beep]...
Caught TimeoutCancellationException

或者,协程库提供了一个方便的 withTimeoutOrNull 函数。你可以使用它来存储函数计算的结果,如果超时则为 null。为了测试它的行为,打开 WithTimeoutOrNullExample.kt 文件并编写以下代码片段:

fun main() = runBlocking {
	val result = withTimeoutOrNull(1300L) {
		repeat(1000) { i ->
			println("$i. Crunching numbers [Beep.Boop.Beep]...")
			delay(500L)
		}
		"Done" // will get canceled before it produces this result
	}
	// Result will be `null`
	println("Result is $result")
}

输出如下:

0. Crunching numbers [Beep.Boop.Beep]...
1. Crunching numbers [Beep.Boop.Beep]...
2. Crunching numbers [Beep.Boop.Beep]...
Result is null