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