Mihai Voicescu
10/26/2021, 6:43 PM@OptIn(ObsoleteCoroutinesApi::class)
class ActorDelegate<T>(coroutineScope: CoroutineScope, create: () -> T) {
class Package<T, R>(val block: T.() -> R) {
val def = CompletableDeferred<R>()
}
private val channel = coroutineScope.actor<Package<T, Any>> {
val t = create()
channel.consumeEach {
it.def.complete(it.block(t))
}
}
suspend fun <R> withContext(block: T.() -> R): R {
val pkg = Package(block)
channel.send((pkg as Package<T, Any>))
val r = pkg.def.await()
return r as R
}
}
fun <T> CoroutineScope.actorDelegate(create: () -> T) = ActorDelegate<T>(this, create)
class CoroutineMutableList(coroutineScope: CoroutineScope) {
private val actorDelegate = coroutineScope.actorDelegate { mutableListOf<Int>() }
suspend fun size() = actorDelegate.withContext { size }
suspend fun contains(element: Int) = actorDelegate.withContext { contains(element) }
suspend fun get(element: Int) = actorDelegate.withContext { get(element) }
suspend fun add(element: Int) = actorDelegate.withContext { add(element) }
// rest...
}
Andreas Scheja
10/26/2021, 7:48 PMby
at the usage site and as such would have to at least provide a operator
*fun* getValue(thisRef: Any?, property: KProperty<*>): T
method, which would already defeat your usage scenario as I don't think this it will allow the suspend
keyword on that method and you won't be able to refer to actorDelegate
as a type of ActorDelegate<T>
but a type of T
private val lock = kotlinx.coroutines.sync.Mutex()
property and then use lock.withLock { <code> }
instead of actorDelegate.withContext { <code> }
Mihai Voicescu
10/27/2021, 8:44 AMActor is more efficient than locking under load, because in this case it always has work to do and it does not have to switch to a different context at all.
A better way of doing it would probably be a ReadWriteLock. Unfortunately there is no ReadWriteLock in coroutines implemented and I don't have the time to implement it now 😢.
Another alternative (which may be better depending on the number of disconnects) is an immutable AtomicReference<Map> that is rebuilt and replaced each time >1 user disconnects.Andreas Scheja
10/27/2021, 9:24 AMsuspend fun getUserData(id: Int): UserData
, as with your current approach if you ask for size()
or contains()
and then try to get()
something from it another coroutine could have modified the map already, leading to undefined behaviour at best. In my classes I then use a sealed interface
to distinguish between these coarser methods with a when
inside the actor's for
loop