I'd like to have a synchronised access to a variable that represents my state from coroutines. How can I fix this?
private var myState: MyState? = null
get() = mutex.withLock {
return#withLock myState
}
set(value) = mutex.withLock {
field = value
}
private val mutex = Mutex()
right now I get Suspend function 'withLock' should be called only from a coroutine or another suspend function message.
If not possible any alternative elegant solutions?
To call suspend function in a non-suspending context. You can use runBlocking.
private var myState: MyState? = null
get() {
return runBlocking {
mutex.withLock {
myState
}
}
}
set(value) {
runBlocking {
mutex.withLock {
field = value
}
}
}
private val mutex = Mutex()
NOTES:
You might be better off changing the property to two suspend functions (getter/setter), instead of using runBlocking.
All depends on the context in which you call myState.
You also want to consider voting for KT-15555.
Related
I have a "high-order" function that have to return some value. Inside that "high-order" function there is an "inside" function which really is a producer of the return value of a "high-order" function.
It is simpler to show it with an example what I mean:
lifecycle.coroutineScope.launch {
val result = doWork()
Log.d("Tag", "some result: ${result.someString}")
}
private val service = SomeService()
suspend fun doWork(): DoWorkResult {
fun onSomeString(someString: String): DoWorkResult {
//some execution
val returnResultForDoWork = DoWorkResult(someString)
//How to return 'returnResultForDoWork' from fun doWork
return returnResultForDoWork
}
service.getString { someString ->
onSomeString(someString)
}
}
class SomeService() {
suspend fun getString(
onResult: (String) -> Unit
) {
delay(1000)
onResult("work is done")
}
}
data class DoWorkResult(val someString: String)
flow execution:
call service.getString
call onSomeString(someString) when a someString is return from service.getString
in onSomeString analyse/handle a someString and return (how?) a DoWorkResult(someString) from doWork
My question is how to return a result of an onSomeString function as a result of a doWork function?
Suspend functions don't need higher order callbacks like that. Really, it's an antipattern, because it restores back "callback hell" that coroutines solve. A proper version of your function would look like:
class SomeService() {
suspend fun getString(): String {
delay(1000)
return "work is done"
}
}
And then your calling function becomes:
suspend fun doWork(): DoWorkResult {
val serviceReturnValue = getString()
//some execution
val returnResultForDoWork = DoWorkResult(serviceReturnValue)
return returnResultForDoWork
}
But let's suppose your service function is not a suspend function, but rather it is asynchronous with a callback, and you don't have control over the source code to make it a suspend function instead.
class SomeService() {
fun getString(
onResult: (String) -> Unit
) {
val handler = Handler(Looper.myLooper())
thread {
Thread.sleep(1000) //simulate some work
handler.post { onResult("work is done") }
}
}
}
Then to be able to return the callback's inner value in a suspend function, you need to convert the asynchronous function into a suspending one. This can be done with suspendCoroutine or suspendCancellableCoroutine. There are many examples you can look up on this site or online, but here's a quick sample. You can write it as an extension function to work like an overloaded version of the asynchronous function.
suspend fun SomeService.getString(): String = suspendCoroutine { continuation ->
getString { continuation.resume(it) }
}
Now you can call this proper suspending version of the function just as in my second code block above.
Honestly, I am not quite sure if I really understand what you try to do but...
is this what you looking for?
private val service = SomeService()
data class DoWorkResult(val someString: String)
suspend fun doWork(): DoWorkResult {
fun onSomeString(someString: String): DoWorkResult {
//some execution
val returnResultForDoWork = DoWorkResult(someString)
//How to return 'returnResultForDoWork' from fun doWork
return returnResultForDoWork
}
return service.getString { someString ->
onSomeString(someString)
}
}
class SomeService {
suspend fun getString(onResult: (String) -> DoWorkResult): DoWorkResult {
delay(1000)
val myStringFromNetworkOrSomething = "work is done"
return onResult(myStringFromNetworkOrSomething)
}
}
I want class UploadWorker to retrieve a value from class Manager, but that value may not yet be ready in Manager. So I want class UploadWorker to wait until that value is set.
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
Manager.isReady()
return Result.success()
}
}
object Manager {
private lateinit var isReady
fun initialize(context: Context, myData: MyData) {
...
isReady = true
}
suspend fun isReady() {
if(::isReady.isInitialized()
return isReady
else // wait here until initialized
}
}
In the else if I could somehow suspend or wait until my MyApplication class calls initialize(). Any ideas?
CompletableDeferred is quite handy for situations like this.
Your code would probably look something like this:
object Manager {
private val initialized = CompletableDeferred<Unit>()
fun initialize(context: Context, myData: MyData) {
...
initialized.complete(Unit)
}
suspend fun awaitInitialized() {
initialized.await()
// initialization is done at this point
}
}
You can use StateFlow to deliver status of initialization:
val isReady = MutableStateFlow(false)
// to wait:
if (isReady.value) return true
else isReady.first { it } // it == true
// to notify:
isReady.value = true
Although StateFlows are quite lightweight, but if you want to do it more lighter but in a ugly way (using coroutine internals directly):
val conts = mutableListOf<Continuation<Boolean>>()
private lateinit var isReady = false
set(value) {
if (value) conts.forEach { it.resume(true) }.also { conts.clear() }
field = value
}
// to wait:
if (isReady) return true
else suspendCancellableCoroutine { cont ->
conts.add(cont)
invokeOnCancellation { conts.remove(cont) }
}
// to notify:
isReady = true
The Code A is based https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/main/java/com/android/example/livedatabuilder/LiveDataViewModel.kt
I modified some code.
I think that emit(timeStampToTime(currentTime.value)) will be correct, but the system report error, so I have to use emit(timeStampToTime(currentTime.value?:0))
1: I'm very strange that why val currentTime isn't Nullable and currentTime.value is Nullable .
2: Why does the original code use liveData { emit(timeStampToTime(it)) } instead of liveData { emit(timeStampToTime(it?:0)) }
Code A
class LiveDataViewModel(
private val dataSource: DataSource
) : ViewModel() {
val currentTime = dataSource.getCurrentTime()
val currentTimeTransformed: LiveData<String> = liveData {
//emit(timeStampToTime(currentTime.value)) // It will cause error
emit(timeStampToTime(currentTime.value?:0))
}
//Original Code
// Coroutines inside a transformation
// val currentTimeTransformed = currentTime.switchMap {
// timeStampToTime is a suspend function so we need to call it from a coroutine.
// liveData { emit(timeStampToTime(it)) }
// }
private suspend fun timeStampToTime(timestamp: Long): String {
delay(500) // Simulate long operation
val date = Date(timestamp)
return date.toString()
}
}
class DefaultDataSource(private val ioDispatcher: CoroutineDispatcher) : DataSource {
override fun getCurrentTime(): LiveData<Long> =
liveData {
while (true) {
emit(System.currentTimeMillis())
delay(1000)
}
}
...
}
It's nullable because there is a constructor
LiveData()
Creates a LiveData with no value assigned to it.
and calling e.g.
val data: LiveData<Long> = LiveData()
data.value
will return null.
Or to consider your code:
liveData {
while (true) {
emit(System.currentTimeMillis())
delay(1000)
}
}
You emit a value immediately, but if you change the order
liveData {
while (true) {
delay(1000)
emit(System.currentTimeMillis())
}
}
then there is no value emitted during the first second and calling currentTime.value will return null.
(Note: is it intentional that you create a new LiveData on every currentTime access?)
So I have a method that binds to the service.
fun bindService() {
val intent = Intent(this, BluetoothService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
Inside onCreate method I use this code:
bindService()
launch {
delay(500L)
service = serviceConnection.serviceBinder?.getService() as BluetoothService
}
Is there more elegant way to wait for the service to be bound than using delay()?
I wrote this just now, and haven't tried it, but hopefully something like it could work. The magic is in suspendCoroutine, which pauses the current coroutine and then gives you a continuation thingy you can use to resume it later. In our case we resume it when the onServiceConnected is called.
// helper class which holds data
class BoundService(
private val context: Context,
val name: ComponentName?,
val service: IBinder?,
val conn: ServiceConnection) {
fun unbind() {
context.unbindService(conn)
}
}
// call within a coroutine to bind service, waiting for onServiceConnected
// before the coroutine resumes
suspend fun bindServiceAndWait(context: Context, intent: Intent, flags: Int) = suspendCoroutine<BoundService> { continuation ->
val conn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
continuation.resume(BoundService(context, name, service, this))
}
override fun onServiceDisconnected(name: ComponentName?) {
// ignore, not much we can do
}
}
context.bindService(intent, conn, flags)
}
// just an example
suspend fun exampleUsage() {
val bs = bindServiceAndWait(context, intent, Context.BIND_AUTO_CREATE)
try {
// ...do something with bs.service...
} finally {
bs.unbind()
}
}
I am trying to return a value generated from coroutine
fun nonSuspending (): MyType {
launch(CommonPool) {
suspendingFunctionThatReturnsMyValue()
}
//Do something to get the value out of coroutine context
return somehowGetMyValue
}
I have come up with the following solution (not very safe!):
fun nonSuspending (): MyType {
val deferred = async(CommonPool) {
suspendingFunctionThatReturnsMyValue()
}
while (deferred.isActive) Thread.sleep(1)
return deferred.getCompleted()
}
I also thought about using event bus, but is there a more elegant solution to this problem?
Thanks in advance.
You can do
val result = runBlocking(CommonPool) {
suspendingFunctionThatReturnsMyValue()
}
to block until the result is available.
You can use this:
private val uiContext: CoroutineContext = UI
private val bgContext: CoroutineContext = CommonPool
private fun loadData() = launch(uiContext) {
try {
val task = async(bgContext){dataProvider.loadData("task")}
val result = task.await() //This is the data result
}
}catch (e: UnsupportedOperationException) {
e.printStackTrace()
}
}