I am attempting to perform parallel API calls with...
# coroutines
j
I am attempting to perform parallel API calls with kotlin. However, the second api call is cancelled when the first call's await() method is called. How do i force all methods to finish executing before returning from the method. The code snippet
Copy code
suspend fun isFinished(token: String): Boolean {
    return coroutineScope {
        val a = async {
            updateProfile(token = token)
        }
        val b = async{
            updateStatus(token = token)
        }
        (a.await()) && (b.await())
    }
}
Methods updateProfile(token: String) and updateStatus(token: String) are the api methods
j
The code looks correct. If the first
await
cancels the other
async
, it probably means the first call failed with an exception. You should check that.
j
There is no exception. If i swap the position of the awaits, then b.await() fails
It also works fine when i use runBlocking
j
How do you tell
b.await()
fails when you swap? Is that not an exception that is thrown?
If it works fine when using
runBlocking
, it could be that both calls are using something that is not thread safe. Did you check that? It would be nice if you could share enough code to reproduce the problem
u
Another point to look at is boolean short circuit. If a returns false, b is never awaited. Currently not sure if your coroutineScope block is enough to compensate that
j
@Joffrey I get an JobCancellationException on the endpoint. That's how I know it fails
However, It finally worked when I did used the approach below.
Copy code
suspend fun isFinished(token: String): Boolean {
    val a = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>).async{
        updateProfile(token = token)
    }

    val b = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>).async{
        updateStatus(token = token)
    }

    val result = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>).async {
        a.await() && b.await()
    }

    return result.await()
}
But I feel there could be a better way
u
I guess it's the boolean short circuit. I guess your coroutineScope block handgs, waiting for b to be awaited until it is cancelled by some outer mechanism.
Try extracting a.await and b.await to variables and then apply && to the variables and everything should work.
j
@Joe Altidore please don't do this. You're creating scopes that you never cancel and just spawn coroutines without structured concurrency, which is usually a bad sign. Also you're creating 3 scopes - why? Did you figure out what was wrong in your initial code? Don't try random stuff without understanding what's wrong in the correct code, otherwise you might just be hiding a deeper problem
l
Also, creating scopes on the fly can lead to coroutines being garbage collected mid-way, which is extremely hard to diagnose. If one of the coroutines gets cancelled, it's because they get cancelled, either from within, or from outside, though it seems it's from within in you case @Joe Altidore. Try replacing your
updateXxx
functions with
delay(…)
calls instead, you'll see it works perfectly with your original snippet.
j
@Joffrey I was very skeptical of the approach too. The
updateProfile()
and
updateStatus()
methods are recursive methods and I am suspecting that to be the reason.
@louiscad I will love to try that but have limited knowledge on the approach you've suggested. More help will be appreciated
l
@Joe Altidore You use this instead:
Copy code
suspend fun isFinished(token: String): Boolean {
    return coroutineScope {
        val a = async {
            delay(3.seconds)
            true
        }
        val b = async{
            delay(2.seconds)
            true
        }
        a.await() && b.await()
    }
}
You'll see that it finishes successfully.
BTW, at the line where you have the
await()
calls, the parenthses around the calls are useless. I edited my snippet to reflect that.
Recursive functions are rareley a good idea, unless you're doing what is sometimes called "sports programming", which is different from writing production code that needs to be reliable and efficient.
j
I guess I should state the use case after all. I have an REST API service that returns a list of items. I need to retrieve the items and store them in a local db. However, the service implements pagination hence the use of recursion.
l
Why would recursion be needed for pagination?
j
I need to ensure that all items are queried successfully before returning a result
j
Still, pagination really is inherently iterative, it feels strange to use recursion for that
j
let me share one of the methods
Copy code
private suspend fun getExpense(offset: Int, token: String): Response{
    val expense = api.syncExpense(offset, token)
    return if(expense is Resource.Success){
        val res = expense.data as ExpenseDto.Data
        if(res.rows.isNotEmpty()){
            expenseDao.addExpenses(res.rows.map { it.toExpenseEntity() })
            if(res.rows.size == LIMIT){
                return getExpense(offset + LIMIT, token)
            }
        }
        res
    }else{
        (expense as Resource.Failure).data!!
    }
}
u
@louiscad what happens if your mocked workloads return false instead?
l
@uli It waits until both are done anyway, just like in the original version.
@Joe Altidore Your code can lead to a very, very awful UX. Today, I was in the train, just like yesterday where I spent around 9 hours there. In such a situation, network keeps dropping packets from time to time because of the speed, which means a long chain like yours is almost guaranteed to break because of a single failure. Instead, just treat your data as paged, saving one thing after another, or doing multiple calls concurrently for multiple pages, so if the network is stable for a few seconds, it's all done.
An alternative is to change the backend so you don't have to do many network calls, and can do a single big one (but not too big of course)
j
I wrote the backend service with ktor and it has the potential of becoming large since its a data synchronisation service
l
What do you want to achieve on the client side? I don't quite get the use case with such a vague function signature (
suspend fun isFinished(token: String): Boolean
)
j
Well, upon login, I try to sync the user data which is obtained via different service (in this case, get Expense and get Funds services which both implements pagination
384 Views