Recently, I came across the following code:
runWithObject(block: suspend CoroutineScope.(myObject: MyClass) -> Unit) {
...
myObject?.let { runBlocking(myDispatcher) { block(it) } }
...
}
Can someone explain to me what the argument type, CoroutineScope.(myObject: MyClass) -> Unit means (and some documentation if possible)? Since I'm not sure what this is, I don't even know how to ask Google XD
It seems like the argument is a block of code that should be run if myObject is non-null. But what's the CoroutineScope. for? Does it mean that the function is something that can only be run in coroutines?
It's a function receiver, explained here. Inside the lambda that you pass to this higher-order function, this is the receiver.
In this case, it means that inside the lambda, you can freely call functions that are members of CoroutineScope (like launch, async, cancel and the coroutineContext property) without specifying the scope.
Related
From my understanding, #RestrictsSuspension defines what suspending functions can be called in a function block. And I'm looking at this code and trying to understand how a #RestrictsSuspension receiver works with a suspending lambda.
A suspending lambda is passed to a function that has #RestrictsSuspension receiver. This annotated receiver says that only its own suspending methods may be called. Yet the passed lambda is suspending, meaning it cannot be called. And this, to me, makes no sense: we're passing a suspending lambda we cannot call.
#RestrictsSuspension
class SomeReceiver {
fun doSomething() {
println("fun doSomething()")
}
}
suspend fun f1(suspendingLambda: suspend SomeReceiver.() -> Unit) {
val receive = SomeReceiver()
receive.fn() // WILL CAUSE AN ERROR
}
fun main() = runBlocking<Unit> {
launch {
fn1 {
//
}
}
}
This gives the error Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope. And this makes sense since I annotated SomeReceiver with #RestrictsSuspension.
This, obviously, is a contrived example that I used to try to understand the concepts. The actual code never directly calls the passed suspending lambda. Within android's source code it does this:
suspendingLambda.createCoroutine(handlerCoroutine, handlerCoroutine).resume(Unit)
So I'm obviously missing something knowledge about coroutines to understand how this can work.
My question is: how I can use #RestrictsSuspension receiver to restrict the suspending functions in a block, while also passing in a suspending lambda?
I am experimenting with coroutines and feel unsure about passing coroutineScope to plain Kotlin UseCase. Can such approach create memory leaks?
Suppose we are initialising our UseCase in VM and will try to pass viewModelScope:
class UploadUseCase(private val imagesPreparingForUploadUseCase: ImagesPreparingForUploadUseCase){
fun execute(coroutineScope: CoroutineScope, bitmap: Bitmap) {
coroutineScope.launch {
val resizedBitmap = withContext(Dispatchers.IO) {
imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
}
}
}
}
Is it safe code? No difference if I would declare this exact code in VM instead?If no, that means I could pass coroutineScope as constructor argument....Now I initially thought that I should create my execute method in a following way:
fun CoroutineScope.execute(bitmap: Bitmap) {
launch {
val resizedBitmap = withContext(Dispatchers.IO) {
imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
}
}
}
}
As far as I understand we use extension function in order for method to use parent coroutineScope. That means, I don't need to pass coroutineScope as argument and just change method to use extension function.
However, in my surprise VM cannot see this method available! Why this method is not available from VM to call?
This is marked as red in VM:
private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
prepareDataForUploadingUseCase.execute(bitmap)
}
This is not marked red from VM:
private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
prepareDataForUploadingUseCase.execute(viewModelScope, bitmap)
}
If my understanding is wrong, why would I use CoroutineScope as extension function instead of passing coroutineScope as function argument?
Passing it as a parameter vs using it as an extension function receiver is effectively the same in the end result. Extension function receivers are basically another parameter that you are passing to the function, just with rearranged syntax for convenience. So you can't use an extension function as a "cheat" to avoid passing a receiver.
But either way, I see it as kind of a clumsy design to have to provide a scope and then hiding the coroutine setup inside the function. This results in spreading coroutine scope manipulation across both sides of the function barrier. The function that calls this function has to be aware that some coroutine is going to get called on the scope it passes, but it doesn't know whether it needs to worry about how to handle cancellation and what it's allowed to do with the scope that it passed.
In my opinion, it would be cleaner to either do this:
suspend fun execute(bitmap: Bitmap) = withContext(Dispatchers.IO) {
imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
}
so the calling function can launch the coroutine and handle the entire coroutine in one place. Or pass no coroutine scope, but have the execute function internally generate its own scope (that is dependent on lifecycleScope or viewModelScope if applicable), and handle its own cancellation behavior. Here's an example of creating a child scope of the lifecycle scope and adding it to some collection of jobs that you might want to cancel under certain circumstances.
fun execute(bitmap: Bitmap) {
lifecycleScope.launch {
bitmapScopes += coroutineScope(Dispatchers.IO) {
imagesPreparingForUploadUseCase.getResizedBitmap(bitmap, MAX_SIZE)
}
}
}
I am answering this specific question: "Why this method is not available from VM to call?"
The method is not available because it takes a receiver (CoroutineScope), but you already have an implicit receiver due to being inside a type declaration: UploadUseCase. Therefore, you cannot just call the second form of the method, because you would somehow have to specify two receivers.
Luckily, Kotlin provides an easy way to do exactly that, the with method.
private fun uploadPhoto(bitmap: Bitmap, isImageUploaded: Boolean) {
with(prepareDataForUploadingUseCase) {
viewModelScope.execute(bitmap)
}
}
However, I would say that this is quite weird, and agree with #Marko Novakovic that you should remove this responsibility from UseCase.
You can pass CoroutineScope as a function parameter, no problem with that. However I would advise you to remove that responsibility from UseCase. Launch coroutines from ViewModel, Presenter etc.
Extension functions are to be called on the instance of extension type. You don't need to call launch {} and withContext inside same function. Do either. launch(Dispatchers.IO) {}.
Extension functions are not just to access parent scope, you can use them for whatever you need them for, you choose.
UPDATE2 - yep, the following extension function does what you need.
suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
kotlinFunction!!.callSuspend(obj, *args)
be nice if the lib doc for callSuspend
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect.full/call-suspend.html
explicitly stated that the receiver if applicable is first in the vararg list. but i'm happy its now possible to do it in 1.3. And it's baked into the Kotlin API now so you longer have to the reflective hack to pull out the backing continuation and invoke the transformed java method through the Java reflection API.
UPDATE - i can see from another stackoverflow question that Kotlin 1.3 has KFunction.callSuspend, anyone know if that can be used in my case and invoked against a reflective method? In which case how can it be called?
val ret = method.kotlinFunction?.callSuspend(/*???*/)
how do you bind the target object? method.invoke takes the target followed by vargargs for the method parameters, but callSuspend only takes varargs.
or is callSuspend just for standalone functions?
I'm writing a fairly sophisticated remoting framework in kotlin where a class implementing an interface (with annotation similar to JAX_RS) can be efficiently remoted over several different transports including HTTP2 and VERTX, and called through a stub proxy implementing the interface so its completely transparent to the calling code. There are reasons i'm writing a custom implementation which i don't need to get into. Everything's based on suspending functions and coroutines - which are awesome.
In order to do this the kotlin interface is used to auto generate a transparent proxy stub on the client side and a dispatcher on the endpoint side. The dispatcher automatically enforces security by looking at security annotations on the interface methods. Identity data can be accessed from the implementation code through the coroutine context.
Everything's working, except the dispatcher obviously has to use reflection to invoke the suspended function on the implementing class. I cannot figure out how to propagate the coroutine context across the reflective suspended invocation. Not only that, the default ThreadPool for coroutines doesn't seem to be used either - instead it uses some fork-join pool.
Coroutines are implemented great in my opinion, but when you start doing the low level stuff you can't avoid the ugly underbelly. The other thing i noticed is a default method in a kotlin interface doesn't map to a default method in the underlying generated java interface. Which also caused my some grief, but thats a seperate issue.
Anyway - if anyone knows how to fix this final issue? Thanks.
// attach an extension function to
suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
suspendCoroutine { cont ->
println("in thread "+Thread.currentThread().name)
val ret=invoke(obj, *args, cont)
cont.resume(ret)
}
//....
withContext(kc) {
// kc NOT propagated through method invocation...
meth.invokeSuspend(rec.ob, args)!!
}
suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
suspendCoroutine { cont ->
val ret=invoke(obj, *args, cont)
cont.resume(ret)
}
There are two main mistakes here:
you expect invoke() to return the value that you must resume the continuation with
you call the user-level suspendCoroutine function instead of the low-level suspendCoroutineUninterceptedOrReturn
Behind these mistakes there may be a deeper misunderstanding of the coroutine suspension mechanism, so let me try to elaborate on that. This is a way to correct your code, taken from the implementation of KCallable.callSuspend:
suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
suspendCoroutineUninterceptedOrReturn { cont -> invoke(obj, *args, cont) }
Note the main feature of this code: it just passes the continuation to the invoked function and never tries to resume it with the result of the invocation.
Now, how does this manage to work? There are two factors:
If the called suspendable function doesn't actually suspend, it will simply return its result, and this will become the result of your invokeSuspend function.
If it does suspend, when it resumes, the suspendable function will on its own use the continuation you passed in and invoke its resume method with the result.
If it decides to suspend, the suspendable function immediately returns the special COROUTINE_SUSPENDED constant. suspendCoroutineUninterceptedOrReturn interprets this value as necessary to cooperate with the coroutine suspension mechanism. Specifically, it makes your function return the same constant to its caller (it does this whether or not your code actually returns the result of the suspendable function). This way the constant propagates all the way up the call stack until it reaches the non-suspendable function that started the coroutine. This is typically an event loop, and now it will be able to go on processing the next event.
how do you bind the target object? method.invoke takes the target followed by vargargs for the method parameters, but callSuspend only takes varargs.
The answer to this is documented under KCallable.parameters:
/**
* Parameters required to make a call to this callable.
* If this callable requires a `this` instance or an extension receiver parameter,
* they come first in the list in that order.
*/
public val parameters: List<KParameter>
So this is how simple it is to implement your invokeSuspend in terms of KCallable.callSuspend:
suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
kotlinFunction!!.callSuspend(obj, *args)
Repository.kt
suspend fun getTestRepository(): X
suspend fun getTestWithParamRepository(a: String): X
Service.kt
fun getTestService(lambda: suspend () -> X) { //... }
Using
Suspend function 'getTestWithParamRepository' should be called only
from a coroutine or another suspend function
getTestService(repository::getTestRepository }
// Surround with lambda
getTestService { repository.getTestWithParamRepository("") }
GL
I am new to kotlin , so need help to understand the code ,I went to a blogs and found something like this and implemented in my code , code work perfect but i can't understand the following things .
Basically , I got lost in how lazyDefferd function , how it's works internally.
a. How generic T is passed .
b. What it mean by this CoroutineScope.() as i know this is input that i need to pass from the ViewModel but how it's getting pass i can't understand .
interface MovieRepository {
suspend fun getTopRatedMovie(page:Int): LiveData<out List<TopRatedMovieEntity>>
}
ViewModel :
class TopRatedMovieViewModel(movieRepository: MovieRepository):ViewModel() {
val topMovie by lazyDefferd{
movieRepository.getTopRatedMovie(1)
}
}
fun <T> lazyDefferd(block:suspend CoroutineScope.()->T):Lazy<Deferred<T>>{
return lazy {
GlobalScope.async(start = CoroutineStart.LAZY) {
block.invoke(this)
}
}
}
a. How generic T is passed.
You can pass it explicitly, e.g.:
val myLazyDeffered = lazyDefferd<SomeType> {
// …
}
But the compiler can usually infer the type, so it's more usual to omit it (unless there's a reason why it's not clear from the code). That's what's happening in your topMovie example: the compiler knows what type the lambda returns, so it infers T from that.
(As you've probably already noted, lazyDefferd() also takes a value parameter, but since it's the last parameter and a lambda, Kotlin lets you omit the parens.)
b. What it mean by this CoroutineScope.()
That's a function literal with receiver. The lambda that you pass to block will behave as if it's an extension method on the CoroutineScope class: inside the lambda, this will refer to a CoroutineScope instance. It's similar to passing the instance as a parameter to the lambda (and in this case, that's how it's called), but the syntax is more concise.
I have read this question but I have a more fundamental question regarding the crossinline keyword. I'm not really sure what problem it is solving and how it solves it.
From the Kotlin Docs,
Note that some inline functions may call the lambdas passed to them as parameters not directly from the function body, but from another execution context, such as a local object or a nested function. In such cases, non-local control flow is also not allowed in the lambdas. To indicate that, the lambda parameter needs to be marked with the crossinline modifier:
[Emphasis added]
This statement is ambiguous to me. First, I am having trouble actually picturing what is meant by "such cases". I have a general idea of what the issue is but can't come up with a good example of it.
Second, the phrase "To indicate that," can be read multiple ways. To indicate what? That a particular case is not allowed? That it is allowed? That non-local control flow in a given function definition is (or is not) allowed?
In short, I have trouble figuring out what the context for using this really is, what using it communicates to clients, and what the expected results of applying this keyword are.
First, I am having trouble actually picturing what is meant by "such cases". I have a general idea of what the issue is but can't come up with a good example of it.
Here's an example:
interface SomeInterface {
fun someFunction(): Unit
}
inline fun someInterfaceBy(f: () -> Unit): SomeInterface {
return object : SomeInterface {
override fun someFunction() = f()
// ^^^
// Error: Can't inline 'f' here: it may contain non-local returns.
// Add 'crossinline' modifier to parameter declaration 'f'.
}
}
Here, the function that is passed to someInterfaceBy { ... } is inlined inside an anonymous class implementing SomeInterface. Compilation of each call-site of someInterfaceBy produces a new class with a different implementation of someFunction().
To see what could go wrong, consider a call of someInterfaceBy { ... }:
fun foo() {
val i = someInterfaceBy { return }
// do something with `i`
}
Inside the inline lambda, return is non-local and actually means return from foo. But since the lambda is not called and leaks into the object i, return from foo may be absolutely meaningless: what if i.someFunction() (and thus the lambda) is called after foo has already returned or even in a different thread?
Generically, 'such cases' means inline functions that call their functional parameters not in their own bodies (effectively, i.e. taking other inline functions into account) but inside some other functions they declare, like in non-inline lambdas and anonymous objects.
Second, the phrase "To indicate that," can be read multiple ways. To indicate what? That a particular case is not allowed? That it is allowed? That non-local control flow in a given function definition is (or is not) allowed?
This is exactly how the problem I described above is fixed in the Kotlin language design: whenever an inline function intends to inline its functional parameter somewhere where it could be not called in-place but stored and called later, the parameter of the inline function should be marked as crossinline, indicating that non-local control flow is not allowed in the lambdas passed here.
Problem: non-local return
Let's first understand the problem of non-local return with a simple example:
fun doSomething() {
println("Before lambda")
doSomethingElse {
println("Inside lambda")
return // This is non-local return
}
println("After lambda")
}
inline fun doSomethingElse(lambda: () -> Unit) {
println("Do something else")
lambda()
}
Non-local return
In the code above, the return statement is called a non-local return because it's not local to the function in which it is called. This means this return statement is local to the doSomething() function and not to the lambda function in which it is called. So, it terminates the current function as well as the outermost function.
Local return
If you just wanted to return from the lambda, you would say return#doSomethingElse. This is called local return and it is local to the function where it is specified.
Problem
Now the problem here is that the compiler skips the lines after the non-local return statement. The decompiled bytecode for the doSomething() looks like following:
public static final void doSomething() {
System.out.println("Before lambda");
System.out.println("Doing something else");
System.out.println("Inside lambda");
}
Notice that there is no statement generated for the line println("After lambda"). This is because we have the non-local return inside the lambda and the compiler thinks the code after the return statement is meaningless.
Solution: crossinline keyword
crossinline
In such cases (like the problem mentioned above), the solution is to disallow the non-local return inside the lambda. To achieve this, we mark the lambda as crossinline:
inline fun doSomethingElse(crossinline lambda: () -> Unit) {
println("Doing something else")
lambda()
}
Non-local return disallowed
When you use the crossinline keyword, you are telling the compiler, "give me an error, if I accidentally use a non-local return inside the nested functions or local objects.":
fun doSomething() {
println("Before lambda")
doSomethingElse {
println("Inside lambda")
return // Error: non-local return
return#doSomethingElse // OK: local return
}
println("After lambda")
}
Now the compiler generates the bytecode as expected:
public static final void doSomething() {
System.out.println("Before lambda");
System.out.println("Doing something else");
System.out.println("Inside lambda");
System.out.println("After lambda");
}
That's it! Hope I made it easier to understand.