Hello, I am somewhat new to coroutines and running...
# coroutines
j
Hello, I am somewhat new to coroutines and running into an issue I don’t understand using
CoroutineStart.LAZY
. It appears, that if I don’t cancel or await a response, the
runBlocking
section never properly terminates. Any ideas?
Copy code
fun main() = runBlocking {
    val lazyDeferredInt: Deferred<Int> = async(start = CoroutineStart.LAZY) {
        5
    }
}

main()

println("##### [scratch] done")
that print statement never executes 😢
Copy code
fun main() = runBlocking {
    val lazyDeferredInt: Deferred<Int> = async(start = CoroutineStart.LAZY) {
        5
    }

    lazyDeferredInt.await()
}

main()

println("##### [scratch] done")
now I see
"##### [scratch] done"
printed * Not a contribution *
p
By defining the deferred as
LAZY
you’re explicitly telling it to not start until required. As such, unless explicitly started or awaited it will not run. The outer
runBlocking
scope will only complete once all child coroutines have completed, which is why you’re not seeing the last println (it’s still waiting for the unstarted async call to complete)
j
Aha! That’s the piece I needed, makes sense, thank you very much.
p
you can always
.cancel()
the deferred if you want to avoid it running in some circumstances
.isActive
will return false if the deferred is either non-started or has finished (or cancelled) - either way, cancelling it in that state should be safe
j
Right, yes I explored that and might be what I go with. Right now I’m working with a flow that looks something like this:
Copy code
fun main() = runBlocking {
  // expensive operations that don't necessarily need to run
  val a: Deferred<T> = async(start = CoroutineStart.LAZY) { ... }
  val b: Deferred<T> = async(start = CoroutineStart.LAZY) { ... }
  val c: Deferred<T> = async(start = CoroutineStart.LAZY) { ... }

  if (<some condition>) {
    a.await()
    b.await()
    c.await()
    // run some logic
  }

  if (<some other condition) {
    a.await()
    // run some other logic
  }

  a.cancel()
  b.cancel()
  c.cancel()
}
But perhaps there is a better way to design this ** Not a contribution
p
you’re better off using
.start
at the beginning of each block so that they’re executed concurrently. Possibly using a helper such as
fun startAll(varargs jobs: Job) = jobs.forEach(Job::start)
. You can also use a helper
async
wrapper such as
Copy code
val deferreds = mutableListOf<Deferred<*>>()
fun <T> lazyAsync(block: suspend CoroutineScope.() -> T) 
    = async(start = CoroutineStart.LAZY, block = block).also(deferreds::add)

val a = lazyAsync { ... }
val b = lazyAsync { ... }
val c = lazyAsync { ... }

if (<some condition>) {
  startAll(a, b, c)
  // logic using a.await() etc where value needed
}

// etc

deferreds.forEach(Job::cancel)
g
Making those coroutines lazy kills parallel execution completely in your case, a, b, c will always run sequentially because of lazy (b is not started until a is finished etc) Personally instead of doing it with lazy, I would just extract starting block of
a, b, c
to own function and
a
to one more function, so it would be easy to read and it would run in paralel
also declaring those as suspend functions will make it a lot more safe (no way someone forget to cancel some future
d
and get hanging code because of this
so essentially directly translating your example I would just do:
Copy code
fun main() = runBlocking {
    if (< some condition >) {
        doABC()
        // run some logic
    }

    if (< some other condition >) {
        doA()
        // run some other logic
    }
}

suspend fun doABC() = coroutineScope {
    // Run all operations in paralel
    listOf(
        async { doA() },
        async { doB() },
        async { doC() }
    ).awaitAll()
}

suspend fun doA() {}
suspend fun doB() {}
suspend fun doC() {}
Ah, I actually see why you may need A, you essentially don’t want to restart it, which is a fair use case. I would probably on practice reshuffle code a bit to avoid this semantics, but even without this with a bit of additional logic it can be instead implemented like:
Copy code
fun main() = runBlocking {
    val aDeferred = async { doA() }
    if ( < some condition >) {
        doABC(aDeferred)
        // run some logic
    }

    if ( < some other condition) {
        aDeferred.await()
        // run some other logic
    }
}

suspend fun doABC(aDeferred: Deferred<Foo>) = coroutineScope {
    listOf(
        aDeferred,
        async { doB() },
        async { doB() }
    ).awaitAll()
}

suspend fun doA() {}
suspend fun doB() {}
suspend fun doC() {}
Not super elegant, but better version would require more information about you actual use case