Home > other >  How can I launch coroutine that doesn't hold the containing function from returning?
How can I launch coroutine that doesn't hold the containing function from returning?

Time:11-06

I have a function that should do its work but also trigger a background process. The triggered process, even though launched within the function, should not hold it from returning because it is not directly related to the work being done.

Code example:

suspend fun coroutiny(): String {
 coroutineScope {
    launch(Dispatchers.IO) {
        delay(1000)
        println("Independent thing that should not avoid coroutiny to return")
     }
 }
 return "Coroutiny return"
}


suspend fun main() {
 println(coroutiny())
 println("after coroutiny")
 delay(2000)
}

My Desired result in this situation would be:

Coroutiny return
after coroutiny
Independent thing that should not avoid coroutiny to return

But what I get is:

Independent thing that should not avoid coroutiny to return
Coroutiny return
after coroutiny

I know this happens because coroutineScope {...} only returns when the coroutines inside return/complete. What I need help with is to know how to do what I want without resorting to GlobalScope.launch {...}, which I wanted to avoid as my use case does not match the documented acceptable use cases for GlobalScope. 

CodePudding user response:

As you noted, the approach with coroutineScope is meant to parallelize work inside your suspend function, but all the work should be finished once you return.

If you want to launch coroutines that live longer than your function's scope, you would need to use some CoroutineScope in which you can launch that coroutine.

The usual approach to get a scope is to make your function an extension on CoroutineScope. It then becomes the responsibility of the caller to provide a coroutine scope to your function (just like launch and async are designed). Also, usually these functions are non-suspending (because suspend functions are expected to finish all the work before returning).

fun CoroutineScope.coroutiny(): String {
    launch(Dispatchers.IO) {
        delay(1000)
        println("Independent thing that should not avoid coroutiny to return")
    }
    return "Coroutiny return"
}


fun main() = runBlocking {
    println(coroutiny())
    println("after coroutiny")
    delay(2000)
}

Output (try it yourself):

Coroutiny return
after coroutiny
Independent thing that should not avoid coroutiny to return

If your function lives inside a component that has a lifecycle, and if it makes sense to tie the life of the launched coroutines to that lifecycle, then another option is to create a CoroutineScope as a property of that component, and cancel it appropriately at the end of the component's lifecycle.

class MyClassWithLifecycle {

    private val scope = CoroutineScope(CoroutineName("my-custom-processing"))

    fun coroutiny(): String {
        scope.launch(Dispatchers.IO) {
            delay(1000)
            println("Independent thing that should not avoid coroutiny to return")
        }
        return "Coroutiny return"
    }


    fun someCloseOrDisposeFunction() {
        scope.cancel()
    }
}

If you're on Android, Kotlin extension libraries provide those scopes out of the box, such as lifecycleScope or viewModelScope in the most important components with lifecycles.

CodePudding user response:

You will need another scope here which lives longer than your original coroutineScope.

val myCustomScope = CoroutineScope(Job())

suspend fun coroutiny(): String {
    myCustomScope.launch(Dispatchers.IO) {
        delay(1000)
        println("Independent thing that should not avoid coroutiny to return")
 }
 return "Coroutiny return"
}

suspend fun main() {
    println(coroutiny())
    println("after coroutiny")
    delay(2000)
    myCustomScope.cancel() // Cancel the scope at the right time
}

Test it here.

Just make sure to cancel myCustomScope when it's no longer needed.

CodePudding user response:

What you're asking for is GlobalScope.launch.

Don't invent another way to do exactly the same thing.

If there's some problem with using the GlobalScope for what you want, then the problem is with wanting to do that, not with using GlobalScope for it.

In particular, in recommending against the use of GlobalScope, the Kotlin team is suggesting that you should manage the lifetime of all these coroutines that you are launching. At a minimum you should ensure that unfinished coroutines don't just accumulate without bound. If the caller's coroutineScope doesn't correctly encapsulate the lifetime of the coroutine, then you should have a different scope that does, and pass that in as an argument.

CodePudding user response:

This was the best way to achieve what I really wanted with the help from the answers I got:

package com.bmw.otd.validation

import kotlinx.coroutines.*

fun coroutiny(scope: CoroutineScope): String {
    scope.launch(Dispatchers.IO) {
        delay(1000)
        println("Independent thing that should not avoid coroutiny to return")
    }

    return "Coroutiny return"
}


suspend fun main() = coroutineScope {
    println(coroutiny(this))
    println("after coroutiny")
}

By doing things this way I assure that:

  • Function does not wait for the completion of the launched coroutine to return.
  • Application doesn't finished until the background work is done, which was not possible to guarantee with a new "nested scope" from my tests.
  • Related