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.
Related
Take the following two samples of code (taken from the Kotlin documentation and from the Coroutines library README respectively):
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(2000)
println("World")
}
println("Hello")
}
In Kotlin >= 1.3.0 one can mark the main() function as suspendable and use a coroutineScope directly.
import kotlinx.coroutines.*
suspend fun main() = coroutineScope {
launch {
delay(2000)
println("World")
}
println("Hello")
}
Both produce the same output:
Hello
World
Are there any functional differences between the two approaches? If yes, what are they?
runBlocking() and coroutineScope() are functionally very similar. They both pause the current execution waiting until the inner coroutine finishes. The difference is that runBlocking() is meant to be executed from a regular non-suspendable code, so it waits by blocking and coroutineScope() is used from suspendable context, so it suspends.
That difference makes them do much different things internally. runBlocking() has to first initialize the whole coroutines machinery before it could execute a suspendable lambda. coroutineScope() has access to existing coroutine context, so it only schedules the inner lambda to be executed and suspends.
Now, suspend fun main() is just a "shortcut" that Kotlin compiler provides to make it easier to initialize coroutines apps. Internally, it creates a bridge between the real main function and your suspendable main function. It generates a separate main() function, which is not suspendable and is the "real" main function. This function initializes a coroutine and uses internal runSuspend() utility to execute your suspend fun main(). This is described here: https://github.com/Kotlin/KEEP/blob/master/proposals/enhancing-main-convention.md#implementation-details-on-jvm-1
Both methods of starting a coroutine application are very similar and you can choose according to your taste. One notable difference is that runBlocking() creates a dispatcher using the current "main" thread. suspend fun main() also executes using the main thread, but it doesn't specify the dispatcher, so whenever you use e.g. coroutineScope() it will switch to Dispatchers.Default. As a result, your runBlocking() example uses single-threaded dispatcher while your coroutineScope() example uses multi-threaded Dispatchers.Default. However, this difference should not really be taken into account when choosing between both methods. We can very easily switch dispatchers at any time.
Take the following two samples of code (taken from the Kotlin documentation and from the Coroutines library README respectively):
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(2000)
println("World")
}
println("Hello")
}
In Kotlin >= 1.3.0 one can mark the main() function as suspendable and use a coroutineScope directly.
import kotlinx.coroutines.*
suspend fun main() = coroutineScope {
launch {
delay(2000)
println("World")
}
println("Hello")
}
Both produce the same output:
Hello
World
Are there any functional differences between the two approaches? If yes, what are they?
runBlocking() and coroutineScope() are functionally very similar. They both pause the current execution waiting until the inner coroutine finishes. The difference is that runBlocking() is meant to be executed from a regular non-suspendable code, so it waits by blocking and coroutineScope() is used from suspendable context, so it suspends.
That difference makes them do much different things internally. runBlocking() has to first initialize the whole coroutines machinery before it could execute a suspendable lambda. coroutineScope() has access to existing coroutine context, so it only schedules the inner lambda to be executed and suspends.
Now, suspend fun main() is just a "shortcut" that Kotlin compiler provides to make it easier to initialize coroutines apps. Internally, it creates a bridge between the real main function and your suspendable main function. It generates a separate main() function, which is not suspendable and is the "real" main function. This function initializes a coroutine and uses internal runSuspend() utility to execute your suspend fun main(). This is described here: https://github.com/Kotlin/KEEP/blob/master/proposals/enhancing-main-convention.md#implementation-details-on-jvm-1
Both methods of starting a coroutine application are very similar and you can choose according to your taste. One notable difference is that runBlocking() creates a dispatcher using the current "main" thread. suspend fun main() also executes using the main thread, but it doesn't specify the dispatcher, so whenever you use e.g. coroutineScope() it will switch to Dispatchers.Default. As a result, your runBlocking() example uses single-threaded dispatcher while your coroutineScope() example uses multi-threaded Dispatchers.Default. However, this difference should not really be taken into account when choosing between both methods. We can very easily switch dispatchers at any time.
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.
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.
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.