Brandon Xia
Brandon Xia

Reputation: 417

Returning from inner nested coroutine by label Kotlin

Use case: I have a lot of operations that I want to happen asynchronously from the main thread but also in parallel with each other.

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)
         }
     }
}

If I try to add a label, like [email protected], editor says "Label is redundant, because it can not be referenced in either ''break'', ''continue'', or ''return'' expression"

Does anyone know a good way of doing this?

Upvotes: 3

Views: 2106

Answers (2)

Animesh Sahu
Animesh Sahu

Reputation: 8096

You can change the name of the lambda using name@ keyword.

Example:

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

This feature isn't properly documented, but some docs like This expressions have demonstration usages in it, and some functions defined in standard library uses it.

Edit: This is actually documented at Return and Jumps.

Upvotes: 2

Sergio
Sergio

Reputation: 30645

If we need to return from a lambda expression, we have to label it and qualify the return. For your case to return from inner coroutine:

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

But we can simplify your case and rewrite code without using a label:

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

// OR

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

If you want to wait for all coroutines to finish and do something on UI thread:

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) }
        }
    }
}

Upvotes: 6

Related Questions