问题
I have a use case where I need to connect and disconnect from a class that acts as a service. Actions can be performed on the service only when the service is connected. Clients are notified when the service connects or disconnects by a callback:
class Service {
    constructor(callback: ConnectionCallback) { ... }
    fun connect() {
        // Call callback.onConnected() some time after this method returns.
    }
    fun disconnect() {
        // Call callback.onConnectionSuspended() some time after this method returns.
    }
    fun isConnected(): Boolean { ... }
    fun performAction(actionName: String, callback: ActionCallback) {
        // Perform a given action on the service, failing with a fatal exception if called when the service is not connected.
    }
    interface ConnectionCallback {
        fun onConnected() // May be called multiple times
        fun onConnectionSuspended() // May be called multiple times
        fun onConnectionFailed()
    }
}
I'd like to write a wrapper for that Service class (that I don't control) using Kotlin Coroutines.
Here is a skeleton of ServiceWrapper:
class ServiceWrapper {
    private val service = Service(object : ConnectionCallback { ... })
    fun connect() {
        service.connect()
    }
    fun disconnect() {
        service.disconnect()
    }
    suspend fun performActionWhenConnected(actionName: String): ActionResult {
        suspendUntilConnected()
        return suspendCoroutine { continuation ->
            service.performAction(actionName, object : ActionCallback() {
                override fun onSuccess(result: ActionResult) {
                    continuation.resume(result)
                }
                override fun onError() {
                    continuation.resumeWithException(RuntimeException())
                }
            }
        }
    }
}
How can I implement this suspendUntilConnected() behavior using Coroutines ? Thanks in advance.
回答1:
Here's how you can implement it:
class ServiceWrapper {
    @Volatile
    private var deferredUntilConnected = CompletableDeferred<Unit>()
    private val service = Service(object : ConnectionCallback {
        override fun onConnected() {
            deferredUntilConnected.complete(Unit)
        }
        override fun onConnectionSuspended() {
            deferredUntilConnected = CompletableDeferred()
        }
    })
    private suspend fun suspendUntilConnected() = deferredUntilConnected.await()
    ...
}
A general note: just because the service got connected at a certain point doesn't guarantee it will still be connected by the time you use it.
回答2:
You're in a suspended function, why not something like:
while (!service.isConnected()) {
    delay(1000)
}
You can put additional timeout conditions into this statement.
来源:https://stackoverflow.com/questions/53906195/suspend-coroutine-until-condition-is-true