Suppose you have class that spawns a coroutine in its constructor to run some sort of background loop. In such a class, you of course have to pass the coroutine scope to the constructor as an argument.
Now you have some functions inside that class that may touch the same states the background loop is touching. This is fine as long as these functions run in the same thread the loop’s coroutine scope is running in. But what if the callers calls these functions from different threads?
One way would be to use coroutine mutexes. I found though that the code becomes really convoluted then. Since in my use case I do not need tons of performance, I opted for forcing all activity to run on the same thread. I came up with code like this:
class ThreadsafeEntity(private val scope: CoroutineScope) {
init {
scope.launch {
println("loop started in thread with ID ${Thread.currentThread().getId()})")
for (i in 0 until 10) {
println("Count $i")
delay(1000)
}
}
}
suspend fun mySuspendingFunction() {
println("mySuspendingFunction called 1 (thread ID ${Thread.currentThread().getId()})")
withContext(scope.coroutineContext) {
println("mySuspendingFunction called 2 (thread ID ${Thread.currentThread().getId()})")
// actual function logic is placed here
}
}
fun myNormalFunction() {
println("myNormalFunction called 1 (thread ID ${Thread.currentThread().getId()})")
runBlocking(scope.coroutineContext) {
println("myNormalFunction called 2 (thread ID ${Thread.currentThread().getId()})")
// actual function logic is placed here
}
}
}
This forces all calls to run in the same thread as the background loop. For suspending functions, withContext
is used. For normal functions, runBlocking
is used. (These normal functions only do stuff that doesn’t block for a long or undefined amount of time, like setting a member variable’s value.)
When you run this, the “called 2” lines show that the function’s actual logic would run in the same thread as the loop, even if the function are called in separate threads (shown by the IDs).
I have two questions:
- Is this okay to do, or is there a smarter way to accomplish this thread safety?
- Is there a better way to make sure
withContext
andrunBlocking
use the context of the coroutine scope? The docs say that directly accessingcoroutineContext
is not recommended.