https://kotlinlang.org logo
#coroutines
Title
# coroutines
b

bj0

02/14/2018, 9:14 PM
the
CompletableDeferred
was completely internal, I was using
return deferred.await()
and
object.onCallback {deferred.complete(it)}
d

dave08

02/15/2018, 3:46 AM
See https://github.com/Kotlin/kotlinx.coroutines/issues/218 , I think that this point is currently very unclear in the docs... but I don't think that is proper use of CompleableDeferred... I think what you need is
invokeOnCompletion
and check the`isCancelled` property on the
CancellableContinuation
provided... see https://github.com/Kotlin/kotlinx.coroutines/blob/master/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CancellableContinuation.kt
Then a Future being converted over can call cancel over there...
b

bj0

02/15/2018, 6:31 AM
I was trying to find a better way than to use
CompletableDeferred
, but I don't see what
invokeOnCompletion
has to do with it. Using a
Future
is effectively the same thing as using a
CompletableDeferred
d

dave08

02/15/2018, 7:19 AM
I mean inside the suspendCancellableCoroutine, it gets completed when it's cancelled, so if you put that listener in there and check if themreason why it was completed was cancellation, you could handle the side effects such as cancelling your future. But all this is assuming that I understand what your trying to accomplish... I thought you were trying to adapt an api that currently returns a regular Future or callback into a suspendable api. That is what all this is for... but a good example of when a CompletableDeferred is used in such adapters is like in retrofit, where theres no choice but to return a Deferred, since the interfaces get implemented by retrofit... like in Jake Wharton's adapter... but there you actually want the Deferred and not to hide it...
b

bj0

02/15/2018, 4:42 PM
Ah yea I wasn't interested in why it was canceled, I was interested in cancelling it from the outside. I'm turning a callback into a coroutine, something like this:
Copy code
var deferred: CompletableDeferred<Item>? = null
    
    suspend fun getItem(): Item {
        deferred = CompletableDeferred<Item>
        someObject.onCallback { deferred.complete(it) }
        return deferred!!.await()
    } 
    
    fun cancelGet() = deferred?.cancel() ?: false
that way the
cancelGet
can be called from another source (like a user hitting the back button)
d

dave08

02/15/2018, 4:46 PM
Why dont you just run cancel on the Job from launch that you're running the suspend function in...?
b

bj0

02/15/2018, 4:50 PM
It's not run from a
launch
, but another
suspend fun
it's actually run from another use button click, there's nothing to cancel on that i'm explicitly creating
d

dave08

02/15/2018, 4:51 PM
But in the end, you have to run a suspend from launch...
or from async or some other coroutine builder that you can cancel... it just doesn't make sense to cancel the suspend...
d

dave08

02/15/2018, 4:53 PM
why do you need to cancel it?
b

bj0

02/15/2018, 4:54 PM
it's waiting on user input, but they can cancel it by clicking the back button
d

dave08

02/15/2018, 5:01 PM
So launch it from there and save the Job for it to be cancelled
b

bj0

02/15/2018, 5:01 PM
I eventually figured out I could just use the continuation like this:
Copy code
var activeCont: CancellableContinuation<Item>? = null


    suspend fun getItem() = suspendCancellableCoroutine<Item> { cont ->
        someObject.onCallback { cont.complete(it) }
        activeCont = cont
    }

    fun cancelGet() = activeCont?.cancel() ?: false
that just moves the creation/cancel of the 'job' from one class to the other, I don't really see a difference
d

dave08

02/15/2018, 5:07 PM
No, a suspendCoroutine fun is just a declaration of what to do when it suspends and resumes... the actual running of it is inside the coroutine builder like launch... even an actor is running inside a job, otherwise you wouldnt be able to run the suspend function in the first place...
For example I have an adapter for xmlrpc:
Copy code
class XmlRpcCoroutineClientImpl(override val client: XmlRpcClient): XmlRpcCoroutineClient {
	override suspend fun <T> awaitExecute(pRequest: XmlRpcRequest): T? =
			suspendCancellableCoroutine {
				client.executeAsync(pRequest, object: AsyncCallback {
					override fun handleResult(pRequest: XmlRpcRequest?, pResult: Any) {
						<http://logger.info|logger.info>("Request '$pRequest' succeeded: {}", pResult.toString())

						it.resume(pResult as? T)
					}

					override fun handleError(pRequest: XmlRpcRequest?, pError: Throwable) {
						logger.error("Request '$pRequest' failed: ", pError)

						it.resumeWithException(pError)
					}

				})
			}
}
In my api I use it to run requests w/ different params:
xmlRpcClient.awaitExecutes(it, Array<Any>::class.java)
But I run it in a launch...
suspendCoroutine is just used for adapting callback based api for use in coroutines...
b

bj0

02/15/2018, 5:13 PM
I know that, but as you saw, in my case it's run from an actor so there's no explicit launch job
what I'm doing with the continuation does work, and it seems cleaner and more self-contained than wrapping things in an extra
launch
at the callsite
d

dave08

02/15/2018, 5:17 PM
But that's just reinventing the wheel... but you choose... 😉
b

bj0

02/15/2018, 5:18 PM
what do you mean? I think any other way of doing it would require even more code/work
d

dave08

02/15/2018, 5:24 PM
Copy code
var currItemGetterJob: Job? = null
// In the actor, simplified...
currItemGetterJob = launch { getItem() }
// In cancel button handler
currItemGetterJob.cancel()
Just make sure that you don't accidentally assign a new job before cancelling the old one...
b

bj0

02/15/2018, 5:30 PM
right, what I'm saying is that's not really much different than what I was doing before, it just moves the job creation from where
getItem
is to the callsite. And in my case the android back button is actually handled in a parent class so it would be more difficult. Plus, if I use the continuation for cancellation like in that last snippet I posted, than I don't have to create any extra jobs, it just cancels the calling scope (as if I just threw
CancellationException
at the callsite)
d

dave08

02/15/2018, 6:07 PM
So just
fun getItem() = async {}
then await for the deferred or cancel it... I think that you're using internal api to do end user work... you might run into pitfalls that were already taken care of for you... you can run 10000 launches and its still very light...
b

bj0

02/15/2018, 6:20 PM
I think I'm using it for what it was created for (wrapping callbacks), I just have an unusual setup with the cancellation callback coming from a different source. I know callbacks are lightweight, I'm mostly concerned about keeping the code clean and small. I also don't have to worry about multiple calls, there can only be a single "get" active at a time, so the way I'm doing it with continuations should be fine
anyway, I think the way I'm doing it makes the most sense for my use-case. I'm effectively wrapping 2 callbacks with a
suspendCancellableCoroutine
, one callback just happens to signal cancellation through an external function call.
6 Views