Emanuel
Emanuel

Reputation: 913

Organizing Kotlin's Coroutines

I often ask myself how to work with coroutines. Every time a button click or some other event comes up I start a coroutine to save or load data from/to a database or a rest api. I then have small functions like below.

In rare cases I get ConcurrentModificationExceptions if two or more coroutines write/read at the same time. I never had this problem with Java+RxJava. Now I only use Kotlin+Coroutines (no RxKotlin, no Flow, no LiveData). As a database I use Room.

Is there a way to hold a reference to something like a Coroutine-Container where I can just add Jobs to get them done one after another? Or how do you guys actually launch your coroutines?

fun loadAllDataForTheUserInterface() {
    viewModelScope.launch {
        val newData = dataBaseRepository.load(...)
        fragment.draw(newData)
    }
}

or

fun handleSaveClick(user: User) {
    viewModelScope.launch {
        val newUser = restApiRepository.uploadNewUser(user)
        databaseRepository.save(newUser)
        fragment.close()
    }
}

My stacktrace

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.next(ArrayList.java:860)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
    at com.google.gson.Gson.toJson(Gson.java:704)
    at com.google.gson.Gson.toJson(Gson.java:683)
    at com.google.gson.Gson.toJson(Gson.java:638)
    at com.google.gson.Gson.toJson(Gson.java:618)
    at my.supercool.app.component.module.JsonModule.toJson(JsonModule.kt:12)
    at my.supercool.app.data.database.MyRoomObjectConverter.fromObjectToJson(MyRoomObjectConverter.kt:29)
    at my.supercool.app.data.database.MyRoomObjectConverter.fromSomeListToJson(MyRoomObjectConverter.kt:71)
    at my.supercool.app.data.database.dao.SomeDao_Impl$1.bind(SomeDao_Impl.java:101)
    at my.supercool.app.data.database.dao.SomeDao_Impl$1.bind(SomeDao_Impl.java:47)
    at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.java:113)
    at my.supercool.app.data.database.dao.SomeDao_Impl$4.call(SomeDao_Impl.java:142)
    at my.supercool.app.data.database.dao.SomeDao_Impl$4.call(SomeDao_Impl.java:137)
    at androidx.room.CoroutinesRoom$Companion$execute$2.invokeSuspend(CoroutinesRoom.kt:61)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at androidx.room.TransactionExecutor$1.run(TransactionExecutor.java:47)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at java.lang.Thread.run(Thread.java:923)

Upvotes: 2

Views: 534

Answers (2)

Emanuel
Emanuel

Reputation: 913

I found out that the actual solution to this problem is to call the code inside a withContext-block. This way your coroutines will be executed one after another an do not compete with each other or worse, like in my case, edit the same resources and cause errors.

I will edit this answer soon if I found out how exactly this works and how to use it. For now here is a link to start with https://www.baeldung.com/kotlin/withcontext-vs-async-await

Upvotes: 1

Sam
Sam

Reputation: 9952

For the examples you gave, I think an actor model would work well. In the actor model, you have one coroutine that runs in a loop to run some tasks. Other parts of the system send messages to the actor when they have some work for it to do.

First, define an actor. The actor coroutine builder function launches a coroutine that will run our tasks, and also creates a Channel that we can use to send tasks to it.

val viewModelTasks = viewModelScope.actor<() -> Unit> {
    for (task in this) {
        task.invoke()
    }
}

When you have a task to execute, send it to the actor:

val myTask = { doSomeWork() }
viewModelTasks.send(myTask)

The actor will loop through the task it receives, finishing each one before it starts the next.

Because the actor is a single coroutine, and will only run one task at a time, it's a good way to protect resources from concurrent access by different threads.

Actors are less well suited when you need to get the result of a task or wait for the task to finish, though. If you need those things you might want to consider a different solution.

Upvotes: 0

Related Questions