从标签 Kotlin 的内部嵌套协程返回

Returning from inner nested coroutine by label Kotlin

用例:我有很多操作想从主线程异步发生,但也希望彼此并行发生。

val scope = CoroutineScope(Dispatchers.IO)
val items = // List of items to do something.

scope.launch {
    items.forEach { item ->
        scope.launch {
            if (itemFailsValidation(item)) {
                // Here I want to skip this item but continue the forEach loop.
                return@launch // "There is more than one label with such a name in this" scope"
            }
            doSomethingThatMightTakeABit(item)
         }
     }
}

如果我尝试添加标签,例如 inner@scope.launch,编辑器会说“标签是多余的,因为它不能在 ''break''、''continue'' 中引用, 或 ''return'' 表达式

有谁知道这样做的好方法吗?

您可以使用 name@ 关键字更改 lambda 的名称。

示例:

scope.launch outerCoroutine@ {
    items.forEach { item ->
        scope.launch {
            if (itemFailsValidation(item)) {
                return@outerCoroutine
            }
            doSomethingThatMightTakeABit(item)
         }
     }
}

此功能没有正确记录,但一些文档如 This expressions 中有演示用法,标准库中定义的一些函数使用它。

编辑: 这实际上记录在 Return and Jumps

如果我们需要从 lambda 表达式 return,我们必须 label it and qualify the return。对于你的情况,从内部协程到 return:

scope.launch {
    items.forEach { item ->
        scope.launch innerScope@ {
            if (itemFailsValidation(item)) {
                return@innerScope
            }
            doSomethingThatMightTakeABit(item)
        }
    }
}

但我们可以简化您的案例并在不使用标签的情况下重写代码:

scope.launch {
    items.forEach { item ->
        if (!itemFailsValidation(item)) {
            scope.launch { doSomethingThatMightTakeABit(item) }
        }
    }
}

// OR

items.forEach { item ->
    if (!itemFailsValidation(item)) {
        scope.launch { doSomethingThatMightTakeABit(item) }
    }
}    

如果您想等待所有协程完成并在 UI 线程上执行某些操作:

scope.launch(Dispatchers.Main) {
    processItemsInBackground()

    // update UI after processing is finished
}

suspend fun processItemsInBackground() = withContext(Dispatchers.IO) {
    // withContext waits for all children coroutines
    items.forEach { item ->
        if (!itemFailsValidation(item)) {
            launch { doSomethingThatMightTakeABit(item) }
        }
    }
}