What makes Iterable.map work with suspend functions? - kotlin

In general, suspend funs cannot be used in place of normal funs. If you try to call a suspend fun directly from a normal fun, you will get a compile-time error.
This blog post mentions that you can do a concurrent map in Kotlin by writing
list.map { async { f(it) } }.map { it.await() }
Why does the second map compile? You can't generally pass a suspend fun in place of a fun. Is it
that map is an inline fun and that the suspension is automatically inferred "upstream"
that map is special cased somehow by Kotlin
something else?

that map is an inline fun and that the suspension is automatically inferred "upstream"
Yes. Suspend funs are checked after inlining. I can't see an explicit mention of this in documentation, but there is one in the Coroutines KEEP:
Note: Suspending lambdas may invoke suspending functions in all places of their code where a non-local return statement from this lambda is allowed. That is, suspending function calls inside inline lambdas like apply{} block are allowed, but not in the noinline nor in crossinline inner lambda expressions. A suspension is treated as a special kind of non-local control transfer.

Related

Calling a suspend function from inside Runnable.run()

I think conceptually this should work. But there is an error:
Suspend function 'dueWorkingHours' should be called only from a coroutine or another suspend function
Is Java and Kotlin parallelization not compatible? Is there a way around this?
Actually, I have the Runnable only to through it into a Handler:
handler.postDelayed(myRunnable, 100)
If there is a similar concept in Kotlin that I could use instead that would be fine too.
It’s not just a matter of Java and Kotlin parallelism being compatible or not. Even in pure Kotlin projects, you can never call suspend functions from outside a coroutine or other suspend function. There has to be some coroutine entry point for the suspend function to have a CoroutineContext, CoroutineScope, and Continuation, which are necessary ingredients for coroutines to work.
If you want to start a coroutine that does something after a delay, you use a CoroutineScope to launch a coroutine and you can call delay() as the first thing you do in that coroutine.

Why can `runBlocking` be invoked without providing a CoroutineContext, if there's no default value for it?

I always check the implementation of the things I use.
Currently I'm using an injection library that doesn't support suspensable functions (Koin), so, only (even if discouraged) for bootstrapping the app, I'm using runBlocking some times.
In order to have richer logs, I'm enriching the coroutine context with some info, yet that info is lost in most context changes (launch, async, and runBlocking among others).
Specially, given the fact that non-suspend methods don't have access to a CoroutineContext, I'm super curious where does runBlocking gets it from.
You can use runBlocking like:
runBlocking {...}
Yet, when I check its implementation, it has two parameters: a CoroutineContext and the suspend block to be executed. None of those parameters have default value, so why can I call it without passing it? I really don't get it!
Additionally, the page says that the default value is EmptyCoroutineContext but the code docs say something about an event loop.
So I ask again? Why can I call it without passing a value, and what's the actual default?
By default runBlocking() starts with an empty coroutine context.
The fact that the context doesn't have a default value is indeed confusing and strange. I think (but I'm not 100% sure) this is because by ctrl+clicking on runBlocking() we go the implementation, so actual definition. But the code is compiled against the expect declaration. I didn't find an easy way to see expect declaration directly in IntelliJ, but it can be found here:
public expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
As we can see, context in this declaration has a default value. Still, this is really confusing when using IntelliJ.
And regarding the mentioned event loop: yes, runBlocking() creates (or re-uses) an event loop, but I don't see how it relates to the coroutine context.
Remember that the context you pass to a coroutine builder like runBlocking or launch is not the context that will actually become available inside the coroutine, but its parent. The builder adds its own items and merges them into the context you provided.
runBlocking uses its own dispatcher, which exists only during the lifetime of the runBlocking function call. You can find this dispatcher in the coroutine context available inside the body of runBlocking, for example using this code:
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
val ctxElems = coroutineContext.fold(mutableListOf<Pair<Any, Any>>()) { list, element ->
list.also { it.add(element.key to element) }
}
for ((key, value) in ctxElems) {
println("${key::class.qualifiedName}: $value")
}
}
}
This prints
kotlinx.coroutines.CoroutineId.Key: CoroutineId(1)
kotlinx.coroutines.Job.Key: "coroutine#1":BlockingCoroutine{Active}#12843fce
kotlin.coroutines.ContinuationInterceptor.Key: BlockingEventLoop#3dd3bcd
(coroutine dispatchers belong to the broader category of continuation interceptors)
The other part of your question, why you don't have to pass in the seemingly non-default parameter, is answered elsewhere. Basically, it's an artifact of the IDE and the expected vs. actual declarations. The relevant declaration is the expected one, and actual is an implementation detail, but the IDE takes you to that one.

Inappropriate blocking method call output stream write

I am using BufferedOutputStream
suspend fun write(byteArray: ByteArray) {
bos.write(byteArray)
}
But when I add suspend keyword I got warning:
Inappropriate blocking method call
What is the correct way to use output stream with coroutines?
OutputStream.write is a blocking function, and by convention suspend functions must never block. You can wrap it in withContext so it uses the IO dispatcher to do it appropriately. However, it is possible this won't make the warning go away because the Kotlin lint is kind of buggy about false positives for this issue.
suspend fun write(byteArray: ByteArray) = withContext(Dispatchers.IO) {
bos.write(byteArray)
}
In general if there is a truly async alternative, it better suits the coroutines model. You could find callback-based APIs and wrap them into suspend functions using suspendCoroutine or suspendCancellableCoroutine.
However, more often than not, you need to deal with actual blocking IO.
In that case, the easiest is to simply run the blocking IO on the IO dispatcher using withContext(Dispatchers.IO):
suspend fun write(byteArray: ByteArray) = withContext(Dispatchers.IO) {
bos.write(byteArray)
}
However, you have to think about which level you're using the IO dispatcher at. If this method is quite low-level, maybe you should use withContext higher in the call stack, and just keep this method non-suspend.

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.

Declare final fun in interface

I'm writing a cooperative multithreading engine in Kotlin. I'm trying to write an interface as follows:
interface Processor {
var suspendAction: (Continuation<Any>) -> Unit
inline suspend fun yield() = suspendCoroutine(suspendAction)
suspend fun process(inbox: Inbox) = Unit
}
yield() is a service I want to provide to all implementors of this interface. Since each virtual call site represents a barrier to inlining, and since each entry point into a suspend fun has its cost, for performance reasons I need this function to be final, but Kotlin doesn't allow me that. I found a workaround in turning yield() into an extension fun:
inline suspend fun Processor.yield() = suspendCoroutine(suspendAction)
I'd like to ask whether use cases as this might motivate the Kotlin language designers to allow final fun in interface.
Note that, unlike in the typical wait-for-IO suspension scenarios, here yield() occurs on a hot, CPU-intensive thread.