Kotlin 协程顺序执行

Any*_*ker 19 kotlin kotlin-coroutines

我正在尝试创建一个对象,它可以在自己的线程中顺序执行一些任务,就像它是一个队列一样。

以下示例仅用于演示我的设置,可能完全错误。

class CoroutinesTest {
    fun a() {
        GlobalScope.launch {
            println("a started")
            delay(1000)
            println("a completed")
        }
    }

    fun b() {
        GlobalScope.launch {
            println("b started")
            delay(2000)
            println("b completed")
        }
    }

    fun complex() {
        a()
        b()
    }
}

fun main() {
    runBlocking {
        val coroutinesTest = CoroutinesTest()

        coroutinesTest.complex()

        delay(10000)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在这段代码打印以下内容

a started
b started
a completed
b completed
Run Code Online (Sandbox Code Playgroud)

这意味着ab并行执行。方法abcomplex可以从不同的线程调用。当然,该complex方法也应该支持这个概念。现在,我需要一种机制,允许我一次只执行一个任务,这样我就可以获得以下输出:

a started
a completed
b started
b completed
Run Code Online (Sandbox Code Playgroud)

我做了一些研究,认为actor使用 aChannel可以做所需的事情,但actor现在被标记为过时问题#87)。我不喜欢使用可能发生变化的 API 的想法,所以我想以通用的方式来做这件事。

Tyl*_*r V 26

TL;DR有几个选项可用于控制顺序协程。

  1. 使用 aChannel使它们按照调用的顺序一次运行一个
  2. 使用 aMutex使它们一次运行一个,但不保证顺序
  3. 使用 a Flow(如BigSt下面的答案中所述)使它们按照调用的顺序一次运行一个,但是请确保流缓冲区足够大,否则如果“正在运行”的作业数量超过 1,则作业可能会丢失大于缓冲区大小。
  4. 如果所需的序列始终相同,请将实际工作放入挂起函数中,并从同一协程范围内调用该序列,以使它们按照代码规定的顺序一次运行一个

渠道

控制执行顺序的一种方法是使用Channel - 将延迟执行的协程作业传递到通道以按顺序运行。与互斥锁不同,通道保证作业按照启动的顺序运行。

class CoroutinesTest {

    private val channel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
        GlobalScope.launch {
            consumeEach { it.join() }
        }
    }

    fun a() {
        channel.trySend(
            GlobalScope.launch(start = CoroutineStart.LAZY) {
                println("a started")
                delay(1000)
                println("a completed")
            }
        )
    }

    fun b() {
        channel.trySend(
            GlobalScope.launch(start = CoroutineStart.LAZY) {
                println("b started")
                delay(2000)
                println("b completed")
            }
        )
    }

    fun complex() {
        // add two separate jobs to the channel,
        // this will run a, then b
        a()
        b()
    }
}
Run Code Online (Sandbox Code Playgroud)

调用complex总是会产生:

a started
a completed
b started
b completed
Run Code Online (Sandbox Code Playgroud)

互斥体

Mutex您可以使用and调用来阻止作业同时运行withLock。如果您在短时间内连续拨打大量电话,则无法保证电话顺序。例如:

class CoroutinesTest {
    private val lock = Mutex()
    
    fun a() {
        GlobalScope.launch {
            lock.withLock {
                println("a started")
                delay(1000)
                println("a completed")
            }
        }
    }

    fun b() {
        GlobalScope.launch {
            lock.withLock {
                println("b started")
                delay(2000)
                println("b completed")
            }
        }
    }

    fun complex() {
        a()
        b()
    }
}
Run Code Online (Sandbox Code Playgroud)

调用complex可以产生:

a started
a completed
b started
b completed
Run Code Online (Sandbox Code Playgroud)

或者:

b started
b completed
a started
a completed
Run Code Online (Sandbox Code Playgroud)

暂停功能

如果您必须始终运行a,那么b您可以使它们都挂起函数并从单个范围内调用它们(仅允许调用complex,不允许单独a调用b)。在这种情况下,complex调用确实保证a在starting之前运行并完成b

class CoroutinesTest {
    
    suspend fun aImpl() {
        println("a started")
        delay(1000)
        println("a completed")
    }

    suspend fun bImpl() {
        println("b started")
        delay(2000)
        println("b completed")
    }

    fun complex() {
        GlobalScope.launch {
            aImpl()
            bImpl()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

调用complex总是会产生:

a started
a completed
b started
b completed
Run Code Online (Sandbox Code Playgroud)