Why the *Async naming convention for functions returning Deferreds? - kotlin

Intellij has an inspection (i.e. lint check) that tells you functions that return Deferred should be named something ending in Async.
Naming conventions like these make sense to me in dynamically typed languages. But Kotlin has such a nice type-checker and ecosystem of tooling, so why rely on the convention?
Especially because Kotlin coroutines bake structured concurrency in, the function will probably also take a CoroutineScope parameter, which would serve the same visual cue at the call site:
suspend fun doStuff() = coroutineScope {
doStuffAsync(this /* CoroutineScope */).await()
//...
}
As a side note, I understand the inspection's message that you'd rarely want a function that returns a Deferred instead of a suspend function. That's not my question. My question assumes that you know what you're doing and you want a Deferred.

To begin with, a function should almost never return a Deferred that comes from an async block. It is up to the caller to wrap some unit of work into an async while doing other work in the foreground, then await on the async result before returning, and wrap all that code in a coroutineScope.
The intended source of Deferred instances is the adaptation layer between Kotlin coroutines and 3rd-party async APIs. For such calls it does make sense to put Async in the name and even some Java APIs follow this convention. For example, you may have a
fun fetchOrder(id: String): Deferred<Order>
and use it as
val orderCancelled = fetchOrder(orderId).isCancelled
This code is type-safe and type-correct, it doesn't cause any compiler errors. It looks like it's fetching an order from a remote system and then checking the order status (whether it's cancelled), but what it's actually doing is getting a Deferred<Order> and checking whether the Deferred is cancelled. Because your function name is missing Async, this kind of error is hard to spot.
Yes, you can also request the IDE to give you the return type, but it may take a while before you even suspect what's going on.

Deferred doesn't return the actual value but is "a light-weight non-blocking future that represents a promise to provide a result later".
As you cannot see this from method name, "we name such functions with the "...Async" suffix to highlight the fact that they only start asynchronous computation and one needs to use the resulting deferred value to get the result".
So the reader of your code can instantly see that your method is not returning the actual value but additionally has to call await() on it.
To your second point about the suspend function:
Usually it's other way round, the xyzAsync() function calls the suspend xyz() function.
As functions returning Deferred can be called from anywhere, not only from suspend functions.

Related

What is the difference between using coroutineScope() and launching a child coroutine and calling join on it?

I am trying to understand the coroutineScope() suspend function in Kotlin and I'm having a hard time understanding the exact purpose of this function.
As per the kotlinlang docs,
This function is designed for parallel decomposition of work. When any
child coroutine in this scope fails, this scope fails and all the rest
of the children are cancelled (for a different behavior see
supervisorScope). This function returns as soon as the given block and
all its children coroutines are completed.
But I feel this behavior can be achieved by launching a child coroutine and calling join on it.
So for example
suspend fun other() {
coroutineScope {
launch { // some task }
async { // some task }
}
}
This can be written as (scope is a reference to the scope created by the parent coroutine)
suspend fun other(scope: CoroutineScope) {
scope.launch {
launch { // some task }
async { // some task }
}.join()
}
Is there any difference between these two approaches since it looks
like they will produce same result and also seem to work in the same fashion?
If not, is coroutineScope merely a way to reduce this
boilerplate code of passing scope from parent coroutine and
calling join on child coroutine?
TLDR
Using CoroutineScope as in the example adds boilerplate code, is more confusing, error-prone and may handle cases like errors and cancellations differently. coroutineScope() is generally preferred in such cases.
Full answer
These two patterns are conceptually different and are used in different cases. Coroutines are all about sequential code and structured concurrency. Sequential means we can write a traditional code that waits in-place, it doesn't use callbacks, etc. and at the same time we don't get a performance hit. Structured concurrency means concurrent tasks have their owners, tasks consists of smaller sub-tasks that are explicit to the framework.
By mixing both above together we get a very easy to use and error-proof concurrency model where in most cases we don't have to launch background jobs and then manage them manually, watch for errors, handle cancellations, etc. We simply fork into sub-tasks and then join them in-place - that's all.
In Kotlin this is represented by suspend functions. Suspend functions are always executed within some context, this context is passed everywhere implicitly and the coroutines framework provides utils to use this context easily. One of the most common patterns is to fork and then join and this is exactly what coroutineScope() does. It creates a scope for launching sub-tasks and we can't leave this scope until all children are successful. We don't have to pass the scope manually, we don't have to join, we don't have to pass errors from children to their siblings and to parent, we don't have to pass cancellations from the parent to children - this is all automatic.
Therefore, suspend functions and coroutineScope() should be the default way of writing concurrent code with coroutines. This approach is easy to write, easy to read and it is error-proof. We can't easily leak a background task, because coroutineScope() won't let us go anywhere. We can't mistakenly ignore errors from background tasks. Etc.
Of course, in some cases we can't use this pattern. Sometimes, we actually would like to only launch a long-running task and return immediately. Sometimes, we don't consider the caller to be the owner of the task. For example, we could have some kind of a service that manages its tasks and we only schedule these tasks, but the service itself owns them. For these cases we can use CoroutineScope.
By using the scope explicitly we can launch tasks in the different context than the current one or from outside of coroutine world. We generally have more control, but at the same time we partially opt-out of the code correctness guarantees I mentioned above. For example, if we forget to invoke join() we can easily leak background tasks or perform operations in unexpected order. Also, in your case if the coroutine invoking other() is cancelled, all launched operations will be still running in the background. For these reasons, we should use CoroutineScope explicitly only if needed.
Common patterns
As a result of all that was said above, when working with coroutines we usually use one of these patterns:
Suspend function - it runs within the caller context and it waits for all its subtasks, it doesn't launch anything in the background.
Function receiving CoroutineScope either as a param or receiver - usually, that means the function wants to do something with the context even after returning (because otherwise it could be simply a suspend function). It either launches some background tasks or stores the context somewhere for a later use.
Regular function that uses its own CoroutineScope to launch tasks. Usually, this is some kind of a service that keeps its custom context.
At least to me, function which is suspend and receives CoroutineScope is pretty confusing, it is not entirely clear what to expect from it. Will it execute the operation in the caller context or in the provided one? Will it wait to finish or only schedule the operation in the background and return immediately? Maybe it will do both: first do some initial processing synchronously (therefore suspend), but also schedule additional task in the background (therefore scope: CoroutineScope)? We don't know this, we have to read the documentation or source code to understand its behavior. Your second example is unnecessary complication over a simple suspend function.
To further make my point consider this example:
data class User(
val firstName: String,
val lastName: String,
) {
fun getFullName(user: User) = ...
}
This example is far from perfect, but the main point is that it is confusing why we have to pass user to getFullName() if we call this function on a user already. We don't know whether it returns a full name of the passed user, the user we invoked the function on or maybe some kind of a mix? If that would be a member function not receiving a User or a static utility function receiving a User, everything would be clear. But a member function receiving a User is simply confusing. This is similar to your second example where we pass the context both implicitly and explicitly and we don't know which one is used and how exactly.

Is it OK to use redundant/nested withContext calls?

I have a personal project written in Kotlin, and I developed a habit of using withContext(...) very generously. I tend to use withContext(Dispatchers.IO) when calling anything that could possibly be related to I/O.
For example:
suspend fun getSomethingFromDatabase(db: AppDatabase) = withContext(Dispatchers.IO) {
return // ...
}
suspend fun doSomethingWithDatabaseItem(db: AppDatabase) {
val item = withContext(Dispatchers.IO) {
getSomethingFromDatabase(db)
}
// ...
}
You can see a redundant withContext(Dispatchers.IO) in the second function. I'm being extra cautious here, because I might not know/remember if getSomethingFromDatabase switches to an appropriate context or not. Does this impact performance? Is this bad? What's the idiomatic way of dealing with Dispatchers?
Note: I know that it's perfectly fine to switch between different contexts this way, but this question is specifically about using the same context.
You do not need withContext for anything besides calling code that demands a specific context. Therefore withContext(Dispatchers.Main) should only be used when you're working with UI functions that require the main thread. And you should only use withContext(Dispatchers.IO) when calling blocking IO related code.
A proper suspend function does not block (see Suspending convention section here), and therefore, you should never have to specify a dispatcher to call a suspend function. The exception would be if you're working with someone else's code or API and they are using suspend functions incorrectly!
I don't know what your AppDatabase class is, but if it is sensibly designed, it will expose suspend functions instead of blocking functions, so you should not need withContext to retrieve values from it. But if it does expose blocking functions for retrieving items, then the code of your first function is correct.
And your second function definitely doesn't need withContext because it's simply using it to call something that I can see is a suspend function.
As for whether it's OK to use redundant context switching...it doesn't hurt anything besides possibly wasting a tiny bit of time and memory context switching and allocating lambdas for no reason. And it makes your code less readable.

Should a library function be suspend or return deferred

Let's assume I'm writing a library that returns a string which is a complex and long running task.
I can chose between offering this
interface StringGenerator {
suspend fun generateString(): String
}
or
interface StringGenerator {
fun generateString(): Deferred<String>
}
Are there any (dis-)advantages of either of the options and which are they? Which should I choose?
Kotlin coroutines are designed along the "sequential by default" guideline. That means that your API should always expose suspend funs and the user, if and when they really need it, can easily wrap them in async.
The advantage of that is analogous to the advantages of cold flows with respect to hot flows: a suspendable function is active only while control is inside it. When it returns, it has not left behind a task running in the background.
Whenever you return a Deferred, the user must start worrying what happens if they don't manage to await on the result. Some code paths may ignore it, the calling code may get an exception, and then their application has a leak.

kotlin coroutines: possible without standard library?

My question is rather theoretical.
I am quite new to kotlin (only passed the tutorial, didn't write any real code).
Reading through the language reference I find myself confused about the fact that "suspend" is a keyword, yet I can't find anything like "launch" in the list of keywords. That makes me think that there is some asymmetry - the "suspend" is a compiler feature, yet "launch" is a library function. Is my understanding correct? If so - wouldn't it have been better to implement both as library features or both as compiler features?
I always thought that you can always write your own standard library using the available language features, but I still can't see if this really applies to this case.
TL;DR: Can I start a coroutine using pure kotlin, without importing any libraries whatsoever (however ugly that would be)?
The suspend marker adds a hidden continuation parameter to the function signature and completely changes the implementation bytecode. Suspension points don't boil down to helper function calls, they turn your linear program code into a state machine, the state being kept in the continuation object. The resulting bytecode isn't even representable as Java program code.
As opposed to that, launch is just regular library code that builds upon the suspend/resume primitive.
#Alexey Soshin's isn't quite correct.
You can use coroutines w/o the library, and it's pretty easy. Here is a about the simplest suspending coroutine example that has 0 dependency on the coroutine library.
import kotlin.coroutines.*
fun main() {
lateinit var context: Continuation<Unit>
suspend {
val extra="extra"
println("before suspend $extra")
suspendCoroutine<Unit> { context = it }
println("after suspend $extra")
}.startCoroutine(
object : Continuation<Unit> {
override val context: CoroutineContext = EmptyCoroutineContext
// called when a coroutine ends. do nothing.
override fun resumeWith(result: Result<Unit>) {
result.onFailure { ex : Throwable -> throw ex }
}
}
)
println("kick it")
context.resume(Unit)
}
This runs fine on the play.kotlinlang.org site.
As you can see from this code, any lambda decorated with suspend has the startCourtine() on it.
In fact, I think the SequenceBuilder() from the standard collection classes uses a simple coroutine like this to generate the sequence, with no dependency on the coroutine library.
The compiler is doing the heavy lifting on the coroutines, splitting the code into different "methods" at each possible suspending point. Look at the java code for this, and you'll see it's "split" into a switch statement. one case before the suspend, and another after.
The library does a ton of nice stuff for you..... and it's likely you'll almost always use it (cuz why not?) but you don't actually need it.
Can I start a coroutine using pure kotlin, without importing any libraries whatsoever (however ugly that would be)?
No. All coroutine generators are inside kotlinx.coroutines library, so you'll need at least that. Now, very theoretically, you could reimplement this functionality yourself. But probably you shouldn't.
How this can be done is a bit too long for a StackOverflow answer, but try invoking method of this Kotlin class from Java:
class AsyncWorks {
suspend fun print() {
println("Hello")
}
}
You'll see that although Kotlin method has no arguments, in Java it requires Continuation<? super Unit>. This is what suspend keyword does. It adds Continuation<T> as the last argument of our function.
wouldn't it have been better to implement both as library features or
both as compiler features?
Ideally, you'd want everything to be a "library feature", since it's easier to evolve. Removing a keyword from a language is very hard. In theory, having suspend as a keyword could be avoided. Quasar, being a framework, uses annotations instead. Go programming language, on the other hand, assumes all functions are suspendable. All those approaches have their advantages and disadvantages.
Kotlin decided to be pragmatic, and add suspend keyword, leaving the decision on the developers. If you're interested in the topic, I highly recommend this talk by Roman Elizarov, author of Kotlin coroutines, that explains their decissions: https://www.youtube.com/watch?v=Mj5P47F6nJg
Answering my own question here.
After a year of Kotlin I tend to think that this IS indeed possible.
The suspend language feature creates an extra class and instantiates it every time your suspend function is called. This class extends ContinuationImpl and stores the progress of your coroutine - to which point it was able to execute so far.
Therefore one will need to write a custom dispatcher that would be able to manage the queue of the continuation objects to decide which one has to run now and a launch function that would take the newly created continuation object and pass it over to the dispatcher.
Now, this is still an asymmetry - the ContinuationImpl lives in kotlin.coroutines.jvm.internal so the compiler assumes this package exists. If one really wants to drop the standard library altogether - he'll need to implement that package to be able use the suspend keyword.
I'm not a kotlin expert though, so I might be wrong.
Because coroutines are valid for use cases that don't support launch. Because suspend requires some specific support from the compiler and launch doesn't if you already have suspend. Because structured concurrency is a library framework on top of the language feature, and launch is a part of that specific framework, that makes specific choices on top of what the language requires.
Starting a coroutine without any libraries can be done with startCoroutine. kotlin.coroutines is part of Kotlin, not a library.

Mark function suspend or using builder

I'm starting with coroutines in Android app. I'm rewriting callbacks to suspendCoroutine<> {} and I've got one dillema: when should I just mark the function as suspend, and when should I wrap the call in some builder (launch, async, etc.)?
Is there some best practice, rule of thumb, or something?
You should write a suspend fun for every asynchronous, callback-based API call you're currently making.
You should wrap in withContext(myThreadPool) every synchronous API call you're making.
All Android-friendly APIs that do I/O use the async approach, so for these you'll be writing suspend funs, but for CPU-intensive tasks (such as decoding images) you may need withContext.
Finally, to be able to use either kind of calls, you must create a top-level coroutine with launch(UI).
Keep in mind that the above is really just a rule of thumb. When you factor your code, many times you realize you need, for example, a suspend fun to make a CPU-intensive operation because there's a withContext somewhere on that call path.
Let me also add a warning about a very typical misuse of the coroutine APIs: you almost never need async-await. Use it only for the cases where you want to truly run it "in the background" while you continue to perform other stuff in your current context. In simpler terms, you should never write
val result = async { calculation() }.await()
Instead you should write
val result = withContext(myThreadPool) { calculation() }