为什么“ withContext”不会在“ runBlocking”下切换协程?

我想找出Kotlin协程的执行顺序和线程切换。我使用withContext来切换到另一个上下文并运行耗时的任务,因此不会阻塞主线程。但是科特林没有按预期切换上下文。

代码在kotlin游乐场上运行:https://pl.kotl.in/V0lbCU25K

无效的情况

suspend fun main() = runBlocking {
    println("Hello,world!!!")
    println(Thread.currentThread().name)
    withContext(Dispatchers.IO) {
        println("Before heavy load: ${Thread.currentThread().name}")
        Thread.sleep(5000)
        println("After heavy load: ${Thread.currentThread().name}")
    }
    println("waiting")
    println(Thread.currentThread().name)
}

输出

Hello,world!!!
main @coroutine#1
Before heavy load: DefaultDispatcher-worker-1 @coroutine#1
After heavy load: DefaultDispatcher-worker-1 @coroutine#1
waiting
main @coroutine#1

以上代码块中的sleep函数在与主线程相同的线程上运行并将其阻塞。


以下情况符合我的预期(耗时的任务不会阻塞主线程)

案例1

suspend fun main() = runBlocking {
    println("Hello,world!!!")
    println(Thread.currentThread().name)
    launch {
        println("Before heavy load: ${Thread.currentThread().name}")
        Thread.sleep(5000)
        println("After heavy load: ${Thread.currentThread().name}")
    }
    println("waiting")
    println(Thread.currentThread().name)
}

输出

Hello,world!!!
main @coroutine#1
waiting
main @coroutine#1
Before heavy load: main @coroutine#2
After heavy load: main @coroutine#2

案例2

suspend fun main() = runBlocking {
    println("Hello,world!!!")
    println(Thread.currentThread().name)
    launch {
        withContext(Dispatchers.IO) {
            println("Before heavy load: ${Thread.currentThread().name}")
            Thread.sleep(5000)
            println("After heavy load: ${Thread.currentThread().name}")
        }
    }
    println("waiting")
    println(Thread.currentThread().name)
}

输出

Hello,world!!!
main @coroutine#1
waiting
main @coroutine#1
Before heavy load: DefaultDispatcher-worker-1 @coroutine#2
After heavy load: DefaultDispatcher-worker-1 @coroutine#2
bonwemeters 回答:为什么“ withContext”不会在“ runBlocking”下切换协程?

withContext实际上并不异步运行,它会合并上下文。

根据Kotlin core docs

withContext

  

使用给定的协程上下文调用指定的暂停块,直到完成为止暂停并返回结果。

因此,使用withContext的这段代码的预期结果:

fun main() = runBlocking {
        println("Before block ${Thread.currentThread().name}")
        withContext(Dispatchers.IO) {
            println("Long op ${Thread.currentThread().name}")
            delay(1000)
        }
        println("After block")
    }

是:

Before block DefaultDispatcher-worker-1
IO? DefaultDispatcher-worker-1
After block

您可以使用协程内部的launch函数来实现所需的目标:

以下代码的结果:

fun main() = runBlocking {
        println("Before block ${Thread.currentThread().name}")
        launch(Dispatchers.IO) {
            println("Long op ${Thread.currentThread().name}")
            delay(1000)
        }
        println("After block")
    }

将是:

Before block DefaultDispatcher-worker-1
After block
IO? DefaultDispatcher-worker-3

TL; DR

使用launch

withContext不执行异步操作。它合并了上下文。要执行异步作业,请在指定的协程上下文中使用启动函数。

,
  

我使用withContext切换到另一个上下文并运行耗时的任务,因此不会阻塞主线程。但是科特林没有按预期切换上下文。

您的withContext调用确实释放了主线程。它把工作转移到了另一个线程,但是那时您的主线程别无选择,只能等待withContext调用完成。 runBlocking启动了一个事件循环,该循环可以服务任何数量的并行协程,但是由于只有一个协程,因此必须完成一个协程才能完成runBlocking块。

以下是该线程未被阻止的说明:

fun main() {
    measureTimeMillis {
        runBlocking {
            launchManyCoroutines()
            println("Top-level coroutine sleeping on thread ${currentThread().name}")
            delay(2_000)
            println("Top-level coroutine done")
        }
    }.also { println("Program done in $it milliseconds") }

}

private fun CoroutineScope.launchManyCoroutines() {
    val cpuCount = getRuntime().availableProcessors()
    (1 until cpuCount).forEach { coroId ->
        launch { // on the main thread
            val sum = withContext(Dispatchers.Default) {
                println("Coroutine #$coroId computing on thread ${currentThread().name}")
                computeResult()
            }
            println("Coroutine #$coroId done on thread ${currentThread().name}:" +
                    " sum = $sum")
        }
    }
    (cpuCount + 1..100).forEach { coroId ->
        launch { // on the main thread
            println("Coroutine $coroId sleeping 1 s on thread ${currentThread().name}")
            delay(1_000)
            println("Coroutine #$coroId done on thread ${currentThread().name}")
        }
    }
}

private fun computeResult(): Int {
    val rnd = ThreadLocalRandom.current()
    return (1..1_000_000).map { rnd.nextInt() }.sum()
}

该程序在所有主线程上启动并发协程(100个以上的可用处理器)。其中一些使用withContext(Dispatchers.Default)在线程池上执行CPU密集型任务(总计一百万个随机整数),而其他一些则直接在主线程上执行挂起工作(延迟一秒钟)。最后,顶级协程在入睡前先睡2秒钟。

整个程序将在2秒钟内完成,并显示如下内容:

Top-level coroutine sleeping on thread main

Coroutine #2 computing on thread DefaultDispatcher-worker-2
Coroutine #3 computing on thread DefaultDispatcher-worker-3
Coroutine #4 computing on thread DefaultDispatcher-worker-5
Coroutine #1 computing on thread DefaultDispatcher-worker-1
Coroutine #5 computing on thread DefaultDispatcher-worker-6
Coroutine #6 computing on thread DefaultDispatcher-worker-4
Coroutine #7 computing on thread DefaultDispatcher-worker-8

Coroutine 9 sleeping 1 s on thread main
Coroutine 10 sleeping 1 s on thread main
...
Coroutine 99 sleeping 1 s on thread main
Coroutine 100 sleeping 1 s on thread main

Coroutine #3 done on thread main: sum = -1248358970
Coroutine #4 done on thread main: sum = -228252033
Coroutine #6 done on thread main: sum = -147126590
Coroutine #2 done on thread main: sum = -1065374439
Coroutine #1 done on thread main: sum = -2029316381
Coroutine #7 done on thread main: sum = -865387844
Coroutine #5 done on thread main: sum = -1695642504

Coroutine #9 done on thread main
Coroutine #10 done on thread main
...
Coroutine #99 done on thread main
Coroutine #100 done on thread main

Top-level coroutine done
Program done in 2066 milliseconds

请注意,程序执行的所有操作都是在主协程睡眠在主线程上时发生的。

本文链接:https://www.f2er.com/3126829.html

大家都在问