3

I am trying to understand structured concurrency in Kotlin and I am unable to wrap my head across this piece of code.

fun main(): Unit = runBlocking {
    other(this)
}

suspend fun other(scope: CoroutineScope) {
    val job = scope.launch {
        scope.launch {
            delay(200)
            println("e")
        }
        println("a")
    }
    job.invokeOnCompletion {
        println("Complete")
    }
}

The code prints

a
Complete
e

While if I replace the inner scope.launch call with launch, like this

suspend fun other(scope: CoroutineScope) {
    val job = scope.launch {
       launch {
            delay(200)
            println("e")
        }
        println("a")
    }
    job.invokeOnCompletion {
        println("Complete")
    }
}

It prints

a
e
Complete

This shows that the first example does not follow structured concurrency since parent job finished before child job. My confusion is, why does this happen?

I felt that scope.launch maybe equivalent to calling launch (which should be equivalent to this.launch and this refers to scope) in this case. But seems like this is not true. Can someone explains why the first one results in unstructured concurrency and what is the difference between the two launch calls? Thanks!

varunkr
  • 5,364
  • 11
  • 50
  • 99
  • I would expect both to provide the exact same result, since in that scenario `scope` is `this`. Unless, a `launch` without `x.launch` defaults to `GlobalScope`, which might be different from the `this` scope OR if you just get a racing condition there with the references. – AlexT Jan 01 '23 at 01:51
  • @Alex.T That's what I expected too. Even if I increase the delay, the result stays the same. I was expecting structured concurrency to be followed in case 1 since no new scope is being created, the same scope is being used to launch the inner coroutine. – varunkr Jan 01 '23 at 02:02
  • 3
    The `this` inside a launch block is a child scope specific to the launched coroutine so it is not the same. – Tenfour04 Jan 01 '23 at 02:47

1 Answers1

6

In the first code, while the inner launch looks like it's a child of the outer launch, it's actually not -- it's a sibling of the outer launch since they were both launched from the same scope. So waiting for the outer launch's job to complete doesn't wait for the inner one.

The second code uses structured concurrency since the inner launch uses the scope created by the outer launch (the receiver of the launch block). In this case it's a child of the outer launch so waiting for the outer job to complete waits for the child to complete as well.

The second one is what you're supposed to do: use the CoroutineScope receiver of the launch block to launch child jobs. Using some other scope instead does not provide structured concurrency.