withContext 用來在現有的 Coroutine 中,使用一個新的 CoroutineContext 來執行一段區塊。最常見的用途是在背景任務完成後,切回主執行緒更新畫面,例如使用 withContext(Dispatchers.Main)。
suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T基本用法#
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外層 Coroutine 先印出 outer: main,接著 launch 內印出 inside: main;進入 withContext(Dispatchers.Default) 之後,執行緒切換到 DefaultDispatcher 的 worker。
withContext 是可取消的#
withContext 的區塊會跟著呼叫它的 Coroutine 一起被取消。
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}")
}執行流程:
- 外層先
delay(100),切到內層 launch,印出inside: main。 - 內層進入
withContext(Dispatchers.Default)並delay(200),又被掛起。 - 外層的 100 毫秒先到,呼叫
job.cancel(),連帶把 withContext 區塊也一起取消,所以Default:那行不會被印出來。 - 最後印出
outer: main。
讓 withContext 不可取消#
如果有些 withContext 區塊(例如更新畫面)不希望被外層取消,可以在 CoroutineContext 中加上 NonCancellable:
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}")
}執行結果:
inside: main
Default: DefaultDispatcher-worker-1
outer: main雖然外層呼叫了 job.cancel(),但 withContext(Dispatchers.Default + NonCancellable) 的區塊仍然會執行完畢。這個性質很適合用在「無論如何都要把畫面更新跑完」之類的情境。
小結#
withContext在現有的 Coroutine 中切到另一個 Context 執行一段程式。- 區塊預設可取消,會跟著外層 Coroutine 一起被取消。
- 加上
NonCancellable可以讓區塊在外層被取消時依然執行完成。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10267189