Kotlin协程在启动和回调中无法执行

3

我以为自己已经足够熟悉Kotlin协程了,直到我看到了这段代码。

1至8都被打印出来了,唯独2没有:

import kotlinx.coroutines.*
import java.lang.Runnable
import java.lang.Thread.sleep
import kotlin.concurrent.thread

fun main() {
    runBlocking {
        Client.createAccount()
        delay(1000)
    }
}

object Client: CoroutineScope {
    override val coroutineContext = newSingleThreadContext("Client")

    fun createAccount() = launch {
        Client2.init(Runnable {
            println('1')
            launch {
                println('2')
            }
            ok()
            ok2()
        })

        println('7')
        launch {
            println('8')
        }
    }

    fun ok() {
        println('3')
        launch {
            println('4')
        }
    }

    fun ok2() = launch {
        println('5')
        launch {
            println('6')
        }
    }
}

object Client2 {

    fun init(runnable: Runnable) = thread {
        sleep(100)
        runnable.run()
    }
}

结果是:
7
8
1
3
4
5
6

回调函数中的协程永远不会被调用。为什么呢? 如果我在createAccount()中删除launch,那么数字1到8都会被打印出来。 另外,如果我使用GlobalScope.launch { println('2') }而不是launch { println('2') },我也可以得到数字2的输出。
2个回答

3
匿名类使用其包装作用域作为父级,所以在Runnable { }中使用launch { println('2') }将在启动父作业createAccount()完成时被取消,因此无法调用,因为它会在launch { println('8') }之后立即被取消。因此,如果您像下面这样更改Client,它将正确地打印出'2'。
object Client: CoroutineScope {
    override val coroutineContext = Dispatchers.Main

    fun createAccount() = launch {
        Client2.init(Run())

        println("7")
        launch {
            println("8")
        }
    }

    fun ok() {
        println("3")
        launch {
            println("4")
        }
    }

    fun ok2() = launch {
        println("5")
        launch {
            println("6")
        }
    }

    class Run: Runnable {
        override fun run() {
            println("1")
            launch {
                println("2")
            }
            ok()
            ok2()
        }
    }
}

你真的解决了我的问题!你能给我一些指导或文档,让我了解包装器作用域的行为吗?谢谢! - sickworm
我不确定...但匿名内部类最初的包装依赖关系是在jvm中,而不是通过协程。如果我错了,请告诉我。 (+我通过转换为Kotlin字节码找出了你的问题) - Choim

0

launch会在Handler中发布一个Runnable,因此它的代码执行并不是立即的。 使用launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED),可以立即在当前线程中执行其lambda表达式。

将分发器更改为当前正在使用的分发器 将lunch从线程内部更改为

launch (coroutineContext, CoroutineStart.UNDISPATCHED)

.

fun createAccount() = launch {

    Client2.init(Runnable {
        println('1')
        launch (coroutineContext, CoroutineStart.UNDISPATCHED){
            println('2')
        }
        ok()
        ok2()
    })

    println('7')
    launch {
        println('8')
    }
}

输出:

7
8
1
2
3
4
5
6

谢谢你给我这个解决方案,它很好用!而且我发现 CoroutineStart.ATOMIC 也起作用,但是我不明白为什么会打印出4和6,而不会打印出2。Choim回答了我的疑问,也许你可以看看。 - sickworm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接