What does a Coroutine Join do? - kotlin

So for example I have the following code:
scope.launch {
val job = launch {
doSomethingHere()
}
job.join()
callOnlyWhenJobAboveIsDone()
}
Job.join() is state as such in the documentation:
Suspends coroutine until this job is complete. This invocation resumes normally (without exception) when the job is complete for any reason and the Job of the invoking coroutine is still active. This function also starts the corresponding coroutine if the Job was still in new state.
If I understand it correctly, since join() suspends the coroutine until its completed, then my code above will do exactly what it wants. That is, the method callOnlyWhenJobAboveIsDone() will only be called when doSomethingHere() is finished. Is that correct?
Can anyone explain further the use case for job.join()? Thanks in advance.
Explaining further my usecase:
val storeJobs = ArrayList<Job>()
fun callThisFunctionMultipleTimes() {
scope.launch {
val job = launch {
doSomethingHere()
}
storeJobs.add(job)
job.join()
callOnlyWhenJobAboveIsDone()
}
}
fun callOnlyWhenJobAboveIsDone() {
// Check if there is still an active job
// by iterating through the storedJobs
// and checking if any is active
// if no job is active do some other things
}
is this a valid usecase for job.join()?

That is, the method callOnlyWhenJobAboveIsDone() will only be called when doSomethingHere() is finished. Is that correct?
Yes.
Can anyone explain further the use case for job.join()?
In your case there is actually no need for another job, you could just write:
scope.launch {
doSomethingHere()
callOnlyWhenJobAboveIsDone()
}
That will do the exact same thing, so it is not really a usecase for a Job. Now there are other cases when .join() is really useful.
You want to run (launch) multiple asynchronous actions in parallel, and wait for all of them to finish:
someData
.map { Some.asyncAction(it) } // start in parallel
.forEach { it.join() } // wait for all of them
You have to keep track of an asynchronous state, for example an update:
var update = Job()
fun doUpdate() {
update.cancel() // don't update twice at the same time
update = launch {
someAsyncCode()
}
}
Now to make sure that the last update was done, for example if you want to use some updated data, you can just:
update.join()
anywhere, you can also
update.cancel()
if you want to.
Whats really useful about launch {} is that it not only returns a Job, but also attaches the Job to the CoroutineScope. Through that you can keep track of every async action happening inside your application. For example in your UI you could make every Element extend the CoroutineScope, then you can just cancel the scope if the Element leaves the rendered area, and all updates / animations in it will get stopped.

Kotlin's Job.join() is the non-blocking equivalent of Java's Thread.join().
So your assumption is correct: the point of job.join() is to wait for the completion of the receiver job before executing the rest of the current coroutine.
However, instead of blocking the thread that calls join() (like Java's Thread.join() would do) it simply suspends the coroutine calling join(), leaving the current thread free to do whatever it pleases (like executing another coroutine) in the meantime.

val queryProduct = GlobalScope.async {
}
val verification = GlobalScope.async {
}
GlobalScope.launch {
verification.join()
queryProduct.join()
}
This is how I use join(). When two asyncs are completed, another launch starts.

Related

Difference between GlobalScope and runBlocking when waiting for multiple async

I have a Kotlin Backend/server API using Ktor, and inside a certain endpoint's service logic I need to concurrently get details for a list of ids and then return it all to the client with the 200 response.
The way I wanted to do it is by using async{} and awaitAll()
However, I can't understand whether I should use runBlocking or GlobalScope.
What is really the difference here?
fun getDetails(): List<Detail> {
val fetched: MutableList<Details> = mutableListOf()
GlobalScope.launch { --> Option 1
runBlocking { ---> Option 2
Dispatchers.IO --> Option 3 (or any other dispatcher ..)
myIds.map { id ->
async {
val providerDetails = getDetails(id)
fetched += providerDetails
}
}.awaitAll()
}
return fetched
}
launch starts a coroutine that runs in parallel with your current code, so fetched would still be empty by the time your getDetails() function returns. The coroutine will continue running and mutating the List that you have passed out of the function while the code that retrieved the list already has the reference back and will be using it, so there's a pretty good chance of triggering a ConcurrentModificationException. Basically, this is not a viable solution at all.
runBlocking runs a coroutine while blocking the thread that called it. The coroutine will be completely finished before the return fetched line, so this will work if you are OK with blocking the calling thread.
Specifying a Dispatcher isn't an alternative to launch or runBlocking. It is an argument that you can add to either to determine the thread pool used for the coroutine and its children. Since you are doing IO and parallel work, you should probably be using runBlocking(Dispatchers.IO).
Your code can be simplified to avoid the extra, unnecessary mutable list:
fun getDetails(): List<Detail> = runBlocking(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}
Note that this function will rethrow any exceptions thrown by getDetails().
If your project uses coroutines more generally, you probably have higher level coroutines running, in which case this should probably be a suspend function (non-blocking) instead:
suspend fun getDetails(): List<Detail> = withContext(Dispatchers.IO) {
myIds.map { id ->
async {
getDetails(id)
}
}.awaitAll()
}

Stop infinite function in kotlin using coroutines - difference between async and GlobalScope.async

I need to wrap some Java-callback function using timeout. Callback may be never called, so it should be interrupted with exception. Here was my first try:
fun main() = runBlocking {
withTimeout(500) {
async {
notCalledCallback()
}.await()
}
Unit
}
private suspend fun notCalledCallback() = suspendCoroutine<Boolean> { cont ->
startScanning(object : SomeCallback {
override fun done() {
cont.resume(true)
}
})
}
fun startScanning(callBack: SomeCallback) {
// callback may never be invoked
// callBack.done()
}
interface SomeCallback {
fun done()
}
I expected to have a TimeoutCancellationException after 500ms, but actually it never happens. However if I replace
async {
notCalledCallback()
}.await()
with
GlobalScope.async {
notCalledCallback()
}.await()
it starts to work. Why? What is the difference between async and GlobalScope.async in this case and why it works in latter case?
while (true) {
Thread.sleep(1)
}
This block of code does not comply with coroutine practices and doesn't offer the coroutine framework any opportunity to cancel it.
A correct implementation of infinityFunction() would be to simply call awaitCancellation. Alternately, you could replace Thread.sleep with delay.
Notably, using GlobalScope actually breaks the correct relationship between your coroutines (making the async block not a child of the calling coroutine), with the result that your main function doesn't wait for infinityFunction() to properly finish cancelling. While this appears to make your code work, it actually just conceals a worse bug.
The answer is actually very simple: suspendCoroutine() is not cancellable. You need to instead use a very similar function: suspendCancellableCoroutine().
Please be aware that ideally you should not only swap one function with another, but also properly cancel the asynchronous operation before resuming the coroutine. Otherwise you leak this background operation as it is entirely detached from your execution context. You can detect cancellations with cont.invokeOnCancellation(), as described in the documentation linked above.
If you use GlobalScope then you await() for the operation in your current execution context, but the operation itself runs in another context. In this case if you cancel, then you cancel waiting, but you don't cancel the operation and you don't care whether it completes or not.

Coroutine launch() while inside another coroutine

I have the following code (pseudocode)
fun onMapReady()
{
//do some stuff on current thread (main thread)
//get data from server
GlobalScope.launch(Dispatchers.IO){
getDataFromServer { result->
//update UI on main thread
launch(Dispatchers.Main){
updateUI(result) //BREAKPOINT HERE NEVER CALLED
}
}
}
}
As stated there as a comment, the code never enters the coroutine dispatching onto main queue. The below however works if I explicitly use GlobalScope.launch(Dispatchers.Main) instead of just launch(Dispatchers.Main)
fun onMapReady()
{
//do some stuff on current thread (main thread)
//get data from server
GlobalScope.launch(Dispatchers.IO){
getDataFromServer { result->
//update UI on main thread
GlobalScope.launch(Dispatchers.Main){
updateUI(result) //BREAKPOINT HERE IS CALLED
}
}
}
}
Why does the first approach not work?
I believe the problem here is that getDataFromServer() is asynchronous, it immediately returns and therefore you invoke launch(Dispatchers.Main) after you exited from the GlobalScope.launch(Dispatchers.IO) { ... } block. In other words: you try to start a coroutine using a coroutine scope that has finished already.
My suggestion is to not mix asynchronous, callback-based APIs with coroutines like this. Coroutines work best with suspend functions that are synchronous. Also, if you prefer to execute everything asynchronously and independently of other tasks (your onMapReady() started 3 separate asynchronous operations) then I think coroutines are not at all a good choice.
Speaking about your example: are you sure you can't execute getDataFromServer() from the main thread directly? It shouldn't block the main thread as it is asynchronous. Similarly, in some libraries callbacks are automatically executed in the main thread and in such case your example could be replaced with just:
fun onMapReady() {
getDataFromServer { result->
updateUI(result)
}
}
If the result is executed in a background thread then you can use GlobalScope.launch(Dispatchers.Main) as you did, but this is not really the usual way how we use coroutines. Or you can use utilities like e.g. runOnUiThread() on Android which probably makes more sense.
#broot already explained the gist of the problem. You're trying to launch a coroutine in the child scope of the outer GlobalScope.launch, but that scope is already done when the callback of getDataFromServer is called.
So in short, don't capture the outer scope in a callback that will be called in a place/time that you don't control.
One nicer way to deal with your problem would be to make getDataFromServer suspending instead of callback-based. If it's an API you don't control, you can create a suspending wrapper this way:
suspend fun getDataFromServerSuspend(): ResultType = suspendCoroutine { cont ->
getDataFromServer { result ->
cont.resume(result)
}
}
You can then simplify your calling code:
fun onMapReady() {
// instead of GlobalScope, please use viewModelScope or lifecycleScope,
// or something more relevant (see explanation below)
GlobalScope.launch(Dispatchers.IO) {
val result = getDataFromServer()
// you don't need a separate coroutine, just a context switch
withContext(Dispatchers.Main) {
updateUI(result)
}
}
}
As a side note, GlobalScope is probably not what you want, here. You should instead use a scope that maps to the lifecycle of your view or view model (viewModelScope or lifecycleScope) because you're not interested in the result of this coroutine if the view is destroyed (so it should just be cancelled). This will avoid coroutine leaks if for some reason something hangs or loops inside the coroutine.

Kotlin coroutines - how to run in background and use result in the caller thread?

The main idea is to have non-suspend function runInBackgroundAndUseInCallerThread(callback: (SomeModel) -> Unit) which run some work asynchronously in background (another thread) and after work is done - run callback in the caller thread (thread that launched runInBackgroundAndUseInCallerThread).
Below I wrote an example code, but I'm not sure how correct it is and whether it is possible at all. With the println("1/2/3/...") I marked the desired call order.
getDispatcherFromCurrentThread - if is possible to implement this function, then solution can be used, but I don't know how to implement it and is it right to do it like that at all.
Therefore, please do not consider it as the only solution.
import kotlinx.coroutines.*
import kotlin.concurrent.thread
fun main() {
println("1")
runInBackgroundAndUseInCallerThread {
println("4")
println("Hello ${it.someField} from ${Thread.currentThread().name}") // should be "Hello TestField from main"
}
println("2")
thread(name = "Second thread") {
runInBackgroundAndUseInCallerThread {
println("5")
println("Hello ${it.someField} from ${Thread.currentThread().name}") // should be "Hello TestField from Second thread"
}
}
println("3")
Thread.sleep(3000)
println("6")
}
fun runInBackgroundAndUseInCallerThread(callback: (SomeModel) -> Unit) {
val dispatcherFromCallerThread: CoroutineDispatcher = getDispatcherFromCurrentThread()
CoroutineScope(Dispatchers.IO).launch {
val result: SomeModel = getModelResult()
launch(dispatcherFromCallerThread) { callback(result) }
}
}
data class SomeModel(val someField: String)
suspend fun getModelResult(): SomeModel {
delay(1000)
return SomeModel("TestField")
}
fun getDispatcherFromCurrentThread(): CoroutineDispatcher {
// TODO: Create dispatcher from current thread... How to do that?
}
Unless the thread is designed to work as a dispatcher there isn't a universal way to make it do so.
The only way which comes to mind is the fact that runBlocking is re-entrant and will create an event-loop in the existing thread, however it will block all non-coroutine code from executing on that thread until it completes.
This ends up looking like:
fun runInBackgroundAndUseInCallerThread(callback: (SomeModel) -> Unit) {
callback(runBlocking(Dispatchers.IO) {
getModelResult()
})
}
dispatcher really is a coroutineContext and it is meaningful when used inside a scope
thus if you want pass dispatcher of parent scope to child scope you can do it.
GlobalScope.launch {
val dispatcher = this.coroutineContext
CoroutineScope(dispatcher).launch {
}
}
therefor getDispatcherFromCurrentThread should be like this.
fun getDispatcherFromCurrentThread(scope: CoroutineScope): CoroutineContext {
return scope.coroutineContext
}
and
GlobalScope.launch {
val dispatcher = getDispatcherFromCurrentThread(this)
CoroutineScope(dispatcher).launch {
}
}
which run some work asynchronously in background (another thread) and after work is done - run callback in the caller thread
First try to answer this question: what is the calling thread supposed to do while the background work is in progress?
Clearly it can't go on to the next line of your code, which is supposed to run after finishing the background work.
You also don't want it to block and wait.
What code should it run, then?
And the only reasonable answer is as follows: the calling thread should, at its topmost level of execution (entry-point function), run an infinite event loop. The code in your question should be inside an event handler submitted to the event loop. At the point you want to wait for the background work, the handler must return so the thread can go on handling other events, and you must have another handler ready to submit when the background work is done. This second handler, corresponding to your callback, is called the continuation and Kotlin provides it automatically. You don't in fact need your own callback.
However, now the most sensitive issue arises: how will you submit the continuation to the event loop? This is not something you can abstract over, you must use some API specific to the event loop in question.
And this is why Kotlin has the notion of a Dispatcher. It captures the case-specific concern of dispatching continuations to the desired thread. You seem to want to solve it without the need to write a dispatcher dedicated to each specific event loop, and unfortunately this is impossible.

Coroutine job never completing

Given this piece of code
fun main() {
val job = Job()
val scope = GlobalScope + job
scope.launch {
println("working!")
delay(1000L)is ms)
println("done!")
// how do i finish the job originally associated with this scope?
}
runBlocking {
job.join()
println("job done")
}
}
I have a custom coroutine scope for my application, and i'm associating a job with this scope like that, reason being i want all the new coroutines that are created from this scope to be the children of this job, if i cancel it i want everything in it to be cancelled.
But main job itself is never completing. How do i complete the main job when the task is done? or failed...
The main job works only as the parent job and will never complete.
But you could wait for all children to complete:
runBlocking {
job.children.forEach { it.join() }
println("job done")
}
Alternatively you should go with Eugene's solution and invoke the join method of the Job you just started, instead of the main job.
Let's simplify your code to something like this:
val job = Job()
runBlocking {
job.join()
}
If you run this code you will see that it also never completes. That is because job.join() suspends until the given job reaches a terminal state which is either completed or canceled (see docs).
When you create a job using some coroutine builder (like .launch {...}) you do not need to complete it by yourself. But since you have created it using a factory method Job() it is now your responsibility to complete it.
You can also find more detailed explanation here.
There are several functions to wait for a Job() object to complete and to cancel it. You may pick one from the list
job.cancel()
job.join()
job.cancelAndJoin()
Only the first function is not a suspend function, so you may call it from every other function, not necessarily a suspend functions
There is a better way - the launch{..} function already returns Job object from the call. You may simplify the code to say
val job = GlobalScope.launch { .. }
that Job object will automatically complete when launch block is over or failed with an exception