suspend fun<T> withContext(context: CoroutineContext,
block: suspend CoroutineScope.()->T):T
withContext
是用来在现有的 coroutine 中,使用新的 CoroutineContext 来建立一个执行区块。我们最常看到的是拿来做更新画面使用,也就是说当 coroutine 执行的任务完成之後,我们可以使用 withContext(Dispatchers.Main)
将执行绪切回主执行绪并更新。
这边我们看一个范例:
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
withContext(Dispatchers.Default) {
println("Default dispatcher: ${Thread.currentThread().name}")
}
}
println("outer: ${Thread.currentThread().name}")
}
outer: main
inside: main
Default dispatcher: DefaultDispatcher-worker-1
→ 根据我们前面所说的巢状架构,这段程序码会先列印最外侧的 outer: main
,接着才会是执行 launch
所建立出来的 coroutine,并依序执行 coroutine 里面的程序。
→ 在 launch
中,我们使用 withContext(Dispatchers.Default)
,也就是说在这个区块中我们将使用 Dispatchers.Default,而从 log 看来的确也已经切换到 DefaultDispatcher。
withContext 所建立出来的区间是可以被取消的,当调用 withContext 的 coroutine 被取消之後,withContext 也就会被取消。如下例:
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
withContext(Dispatchers.Default) {
delay(200)
println("Default: ${Thread.currentThread().name}")
}
}
delay(100)
job.cancel()
job.join()
println("outer: ${Thread.currentThread().name}")
}
inside: main
outer: main
→ 外侧的 coroutine 因为呼叫了 delay()
之後,所以把外侧的 coroutine 切换至等待状态,并寻找下一个可执行的 coroutine。那麽这边我们看到列印出 inside: main
,紧接者进入 withContext(Dispatchers.Default)
,在 withContext 中立刻就呼叫一个 delay() ,所以此 coroutine 又被切换成等待状态,并且找寻下一个可执行的 coroutine。此时,外侧的 coroutine 已经结束它的延时,所以呼叫到了 job.cancel()
,这个时候因为 launch
里面尚有任务还在等待,所以就直接被取消。最後则是列印出 outer: main
。
在前面的范例中,我们知道 withContext 的区块会随着启动它的 coroutine 被取消也跟着被取消,what if 我们不希望让 withContext 被取消呢?
我们可以在 withContext 的 CoroutineContext 中加上 NonCancellable
,加上 NonCancellable 之後,被 withContext 包住的区块就不会因为外侧的 coroutine 取消而跟着被取消。
如下面的范例:
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
withContext(Dispatchers.Default + NonCancellable) {
delay(200)
println("Default: ${Thread.currentThread().name}")
}
}
delay(100)
job.cancel()
job.join()
println("outer: ${Thread.currentThread().name}")
}
→ 这个范例跟前面一个几乎相同,唯一不同的地方在於我加上 NonCancellable
在 withContext 的 CoroutineContext 中,结果会是如何呢?我们看一下:
inside: main
Default: DefaultDispatcher-worker-1
outer: main
虽然 job 的 cancel()
被呼叫,但是 withContext 里面的区块仍然会执行。
在最前面有说,我们经常使用 withContext 在更新画面上,也就是说,我们可以让更新画面这段程序码无论如何都会执行,而不会因为外侧的 coroutine 被取消而跟着被取消。
withTimeout
顾名思义就是跟 timeout 有关系,我们先看他是怎麽使用的:
suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T
→ 它有包含两个参数,一个是时间,另一个是 CoroutineScope 也就是执行的区块。
要如何使用呢?我们看一下下面的范例:
fun main() = runBlocking {
val job = launch() {
println("inside: ${Thread.currentThread().name}")
withTimeout(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
println("withTimeout")
}
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}
→ 在上面这个范例中,同样的我们在 runBlocking
里面使用了 launch
建立了一个 coroutine,而在 launch 的下方则是有几行程序码。
→ 首先,会先执行最外侧的 delay(100)
,这时最外侧的 Coroutine 就被设定为暂停,此时会寻找下一个适当的 coroutine 来执行。在这个范例中,适当的 coroutine 是 launch 的区块。launch 内部有一重复 10 次,在每一次执行的时候,都会将 coroutine 暂停 50 毫秒,在暂停完成之後,会把执行绪使用权切换至外侧,但是外侧的 coroutine 还在等待,所以使用权又切回来执行下一次。如果要完成重复十次的任务,需要花费 10*50 = 500 毫秒。
→ 假设我们希望执行的时间能够在 200 毫秒,在这个 repeat(10){ ... }
的外侧,我们加上了 withTimeout(200)
,当 200 毫秒结束之後,我们就会取消这个区块里面的所有任务。所以这段程序码的输出会是:
inside: main
delay 0 times
delay 1 times
outer: main
delay 2 times
delay 3 times
→ 可以发现, repeat 区块的程序只有跑了四次,符合我们的需求。
其实当 withTimeout 的时间到了之後,是会抛出 TimeoutCancellationException
,只不过由於 TimeoutCancellationException 是 CancellationException
的子类别,所以这个例外会被 coroutine 给吃掉。
但是,如果我们真的需要处理这个例外,我们可以使用 try-catch
来拦截 TimeoutCancellationException。如下:
fun main() = runBlocking {
val job = launch() {
println("inside: ${Thread.currentThread().name}")
try {
withTimeout(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
println("withTimeout")
}
} catch (e: TimeoutCancellationException) {
println(e.message)
}
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}
inside: main
delay 0 times
delay 1 times
outer: main
delay 2 times
delay 3 times
Timed out waiting for 200 ms
将上例的 withTimeout 用 try-catch
包起来之後,我们就可以接收到这个例外了。
如同 withContext,withTimeout 也同样是可以取消的。如下例:
fun main() = runBlocking {
val job = launch() {
println("inside: ${Thread.currentThread().name}")
try {
withTimeout(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
println("withTimeout")
}
} catch (e: TimeoutCancellationException) {
println(e.message)
}
}
delay(100)
job.cancel()
job.join()
println("outer: ${Thread.currentThread().name}")
}
→ 我们在外侧 coroutine delay(100)
之後加上了 job.cancel()
,所以当外侧 coroutine 的暂停时间结束後,就会把内侧的 coroutine 给取消掉。哪麽这个结果会是如何呢?
inside: main
delay 0 times
delay 1 times
outer: main
→ 你应该有猜到,因为 withTimeout 是可以取消的,所以当外侧取消了内侧的 coroutine ,那麽连带 withTimeout 也一并被取消了,所以 repeat 第三次、第四次就被取消没做了。
跟 withTimeout
非常的像,只不过一个是会抛出 TimeoutCancellationException,而另一个是回传 null,我们看下面的范例:
fun main() = runBlocking {
val job = launch() {
println("inside: ${Thread.currentThread().name}")
val timeout = withTimeoutOrNull(600) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
return@withTimeoutOrNull 10
}
println("timeout= $timeout")
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}
→ 跟上面的范例很相像,差异只在於我们在 withTimeoutOrNull(){...}
的最後加上了一个回传值,所以当程序码顺利在时限内完成,就会回传这个值,否则就会回传 null
。
→ 如果 timeout 是 600 毫秒,那这段程序码可以正常跑完,所以结果会是
inside: main
delay 0 times
delay 1 times
outer: main
delay 2 times
delay 3 times
delay 4 times
delay 5 times
delay 6 times
delay 7 times
delay 8 times
delay 9 times
timeout= 10
将 timeout 改成 200 之後,这段程序就没有办法执行 10 次,最多就只能跑 4 次,我们看看结果会是如何?
fun main() = runBlocking {
val job = launch() {
println("inside: ${Thread.currentThread().name}")
val timeout = withTimeoutOrNull(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
return@withTimeoutOrNull 10
}
println("timeout= $timeout")
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}
inside: main
delay 0 times
delay 1 times
outer: main
delay 2 times
delay 3 times
timeout= null
→ 原本输出应该是 10,但是因为 timeout 的关系,所以只能完成四次,所以最後 withTimeoutOrNull
会输出 null
。
本篇文章介绍的三个 suspend 函式, withContext、withTimeout、withTimeoutOrNull。
withContext 适合使用在执行完非同步的呼叫後,需要切换成主执行绪并更新画面的情境,而另外两个与 timeout 有关的函式,主要就是要看使用者的需求,如果超时之後就不管他,可以使用 withTimeout,如果执行的区块是有一个回传值,withTimeoutOrNull 或许就比较适合了。
>>: Leetcode: 26. Remove Duplicates from Sorted Array
Event Handling是甚麽呢? Event Handing是可以用v-on指令监听DOM事件...
本文同步更新於blog 情境:让我们用Line群组,来实作观察者模式 首先实作抽象的观察者类别 (...
基本元组 元组的结构跟串列是一样的,但元组可以更安全的保护资料,因为它的资料不会被改变,而且元组的...
这是在Vue官网提供的式意图: 红框白底的是各个钩子函式的名称,这些钩子代表 Vue 实体的每个阶段...
前一篇IMPORT中,提到Genero Package中有提供一些预先制作的功能套件可用。 可是面对...