kotlinasynchronouskotlin-coroutinescoroutinecoroutinescope

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 inner@scope.launch, 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?


Solution

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