With the following: 1. `withContext(currentCorout...
# coroutines
t
With the following: 1.
withContext(currentCoroutineContext()) { /*...*/ }
(alternatively
coroutineScope { /*...*/ }
, which should be the same in behaviour) 2.
with(CoroutineScope(currentCoroutineContext())) { /*...*/ }
The difference in behaviour is that, in (1)
withContext
does not return until all child coroutines started under its block have completed; whereas (2) does not wait for its child coroutines to complete. For example:
Copy code
runBlocking{
    //(1)
    withContext(currentCoroutineContext()) {
        launch{
            delay(100)
            print("1")
        }
    }
    print("2")
}
//prints "12"
and
Copy code
runBlocking{
    //(2)
    with(CoroutineScope(currentCoroutineContext())) {
        launch{
            delay(100)
            print("2")
        }
    }
    print("1")
}
//prints "12"
* My question is, nobody ever does (2), the "non child coroutine waiting" variant, but why? Could we say that nobody does (2) because it violates the coroutine library's design philosophy where concurrency must be explicit?
Okay I figured, it's because it is unnecessary, and my question is stupid... (2) could just be:
Copy code
runBlocking{
    launch{
        delay(100)
        print("2")
    }
    print("1")
}
And by coroutines convention, we should either do:
Copy code
fun CoroutineScope.f1(){
    launch{
        delay(100)
        print("2")
    }
    print("1")
}
for immediate return, or:
Copy code
suspend fun f2() = coroutineScope{
    launch{
        delay(100)
        print("1")
    }
    print("2")
}
for coroutine execution suspension
☝️ 1
u
And as far as I know, your
CoroutineScope(currentCoroutineContext())
could be garbage collected before the jobs are complete. Whenever you create a new CoroutineScope, you should also hold a strong reference to it.
1
t
Are you sure about that? I presume that the coroutine library would hold strong references to each and every single running coroutine under the hood. How else do they handle suspension and continuation otherwise?
s
Could we say that nobody does (2) because it violates the coroutine library's design philosophy where concurrency must be explicit?
Yes, exactly. By convention, a suspending function always waits for all its work to finish before returning. Your summary of the original problem, and the two correct patterns in your later message, look spot on to me.
t
Come to think of it, our tracing garbage collector can only collect objects not reachable by any thread, so even a simple running runnable (like those in Java), when not referenced by any of our code explicitly is internally referenced by the thread it is running on. So they should be safe from GC, yes? 🤔
s
I don't believe there's any issue with garbage collection. A coroutine holds a reference to its context. The
CoroutineScope
instance itself might become unreachable, but it's only a wrapper for the
coroutineContext
inside.
👀 1
☝🏻 1
u
Here is a documented issue (but only for NonCancelable coroutines): https://github.com/Kotlin/kotlinx.coroutines/issues/1061 And here a discussion from this slack channel: https://kotlinlang.slack.com/archives/C1CFAFJSK/p1635788182098400 I have not yet found an authoritative reference for the general (not NonCancelable) case.
1
😮 1
👀 2
l
True story
s
Thanks for the links!
l
true story
t
this is super interesting
Still I don't think the bug is affecting the example case here, if I understand the issue correctly
s
Yes, as far as I can see, all your code examples preserve the
Job
hierarchy. Since the parent job keeps a reference to the child, I don't think the linked issue would affect your code.
l
I wouldn't make my code rely on the order of the things you showed. There are ways to ensure something happens after another thing, coroutines are definitely not short on solutions for that.
t
yeah I see what you mean 👌 the functions are just to show that one of them returns immediately