I am following a tutorial where the teacher uses the ProducerJob method to demonstrate kotlin coroutine but my IDE says that method is deprecated and I should use ReceiveChannel instead but that also says I should use a extensions on the coroutinescope instead but I can seem to be unable to implement that correctly.
Here the instructor's code:
fun produceNumbers() : ProducerJob<Int> = produce {
for (x in 1..5) {
println("send $x")
send(x)
}
println("Done")
}
fun main() = runBlocking{
val channel = produceNumbers()
channel.consumeEach {
println(it)
}
println("Main done")
}
And here is my code:
fun produceNumbers() : ReceiveChannel<Int> = produce {
for (x in 1..5) {
println("send $x")
send(x)
}
println("Done")
}
fun main() = runBlocking{
val channel = produceNumbers()
channel.consumeEach {
println(it)
}
println("Main done")
}
Does your code compile: Yes
Version: kotlinx-coroutines-core-0.27.0.eap13
The reason why it is recommended to declare your function as extension of CoroutineScope is that you need a scope for any coroutine-creating built-in function anyway. You should not be able to call produce without a scope, it should not compile in recent versions of Kotlin coroutines library.
Declaring your function as extension of CoroutineScope is done this way:
fun CoroutineScope.produceNumbers() : ReceiveChannel<Int> = produce {
for (x in 1..5) {
println("send $x")
send(x)
}
println("Done")
}
If you are not using it in an extension of CoroutineScope then you'll need to either provide a scope another way (like a field of an enclosing class, or method param), or use the GlobalScope, both of which are not recommended. The extension-based version above is a pattern that is recognizable and intuitive for coroutine users.
A more "reactive" approach would be using flows instead of channels:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun produceNumbers() : Flow<Int> = flow {
for (x in 1..5) {
println("emit $x")
emit(x)
}
println("Done")
}
fun main() = runBlocking {
val flow = produceNumbers()
flow.collect {
println(it)
}
println("Main done")
}
Note that this will behave differently though, as it wont be started in a concurrent coroutine unless explicitly stated (e.g. using
flowOn(Dispatchers.Default)
, or replacing the flow {...} by a channelFlow {...}.
Related
Recently I've updated my project to Kotlin 1.5 and noticed some weird and unexpected behaviour when combining Flow with Result. Consider the following example:
// Kotlin 1.5.0
// Coroutines (kotlinx-coroutines-core) 1.5.0
class Foo
interface FooListener {
fun onSuccess(foo: Foo)
fun onFailure(error: Throwable?)
}
suspend fun collectData(listener: FooListener) {
flow { emit(Result.success(Foo())) }
.collect { result -> // for reference
when {
result.isSuccess -> listener.onSuccess(result.getOrThrow())
result.isFailure -> listener.onFailure(result.exceptionOrNull())
}
}
}
fun main() {
runBlocking {
collectData(object : FooListener {
override fun onSuccess(foo: Foo) {
println(foo)
}
override fun onFailure(error: Throwable?) {
println(error)
}
})
}
}
The above compiles but fails in runtime with:
Exception in thread "main" java.lang.ClassCastException: kotlin.Result cannot be cast to Foo
After some debugging I noticed that the collected value (result) appears to be of type Result<Result<Foo>> instead of the expected Result<Foo>:
suspend fun collectData(listener: FooListener) {
flow { emit(Result.success(Foo())) }
.collect { result ->
println(result) // Output: Success(Success(Foo#...))
when {
result.isSuccess -> listener.onSuccess(result.getOrThrow())
result.isFailure -> listener.onFailure(result.exceptionOrNull())
}
}
}
The problem disappears when I give up an idea of using externally provided listeners or callback functions in collectData and, for example, use something defined globally:
class Foo
interface FooListener {
fun onSuccess(foo: Foo)
fun onFailure(error: Throwable?)
}
val listener = object : FooListener {
override fun onSuccess(foo: Foo) {
println(foo)
}
override fun onFailure(error: Throwable?) {
println(error)
}
}
suspend fun collectData() {
flow { emit(Result.success(Foo())) }
.collect { result ->
println(result) // Output: Success(Foo#...)
when {
result.isSuccess -> listener.onSuccess(result.getOrThrow())
result.isFailure -> listener.onFailure(result.exceptionOrNull())
}
}
}
fun main() {
runBlocking {
collectData()
}
}
The above runs without any errors.
At this point it seems to me like a bug in the language, but I've been onto this for so long I'm not 100% sure and I could use a second opinion before bringing it up on Kotlin YouTrack.
I've seen Why does Kotlin crash passing generic Result parameter? and JVM / IR: ClassCastException with Result object when it is used by a generic method in a suspend call but the bug not only looks the other way around, but it is supposed to be fixed in the newest version as well.
This is tracked in https://youtrack.jetbrains.com/issue/KT-46915 and looks like the fix will be released with Kotlin 1.5.30
I have The same issue I downgraded the kotlin version from 1.5.10 to 1.4.32 and the code run correctly.
I have this code:
interface Film {
suspend fun total(): Int
}
suspend fun getFilms() : List<Film> {
return films.sortedBy { it.total() }
}
But I get an error because I'm calling Film::total method inside non-suspension function (sortedBy selector). How can I solve this?
suspend fun getFilms(): List<Film> {
return films
.map { it to it.total() }
.sortedBy { it.second }
.map { it.first }
}
I guess it is just a warning, anyways you can force it to run using runBlocking
suspend fun getFilms() : List<Film> {
val films = arrayListOf<Film>()
return films.sortedBy { runBlocking { it.total() } }
}
The error states it clearly "Suspension functions can be called only within coroutine body". You are invoking total() from within sortedBy thus the error.
You should give more context in order for me to be able to provide a more accurate answer. Having such little context you can remove the suspend from total() to let the code compile. Anyway just try this:
suspend fun getFilms() = films.sortedBy { it.total() }
If you cannot change the interface what about something like that:
data class MyFilm(val total: Int) : Film {
override suspend fun total(): Int = total
}
suspend fun getFilms(): List<Film> = withContext(Dispatchers.Default) {
films.sortedBy { it.total }
}
You can wrap #IR42's answer in an extension function to make code a bit more readable if you use this in multiple places.
suspend inline fun <T, R : Comparable<R>> Iterable<T>.sortedBySuspending(
crossinline selector: suspend (T) -> R?
): List<T> = this
.map { it to selector(it) }
.sortedBy { it.second }
.map { it.first }
If I have a List<A> and a function suspend (A) -> B, how can I apply this function on the list in parallel?
coroutineScope {
list.map {
async {
process(it)
}
} // List<Deferred<B>>
.awaitAll() // List<B>
}
suspend fun process(a: A): B {
...
}
This assumes you are already in a suspend context. Otherwise, you need to launch a new coroutine on the appropriate scope instead of using the coroutineScope scoping function.
You can create an extension function on CoroutineScope, go through each element of the list and launch a coroutine for each element. In this way elements of the list will be processed in parallel. Some code snippet:
fun CoroutineScope.processListInParallel(list: List<A>): List<Deferred<B>> = list.map {
async { // launch a coroutine
processA(it)
}
}
GlobalScope.launch {
val list = listOf(A("name1"), A("name2"), A("name3"))
val deferredList = processListInParallel(list)
val results: List<B> = deferredList.awaitAll() // wait for all items to be processed
}
suspend fun processA(a: A): B {
delay(1000) // emulate suspension
return B("Result ${a.name}")
}
data class A(val name: String) {}
data class B(val name: String) {}
Note: GlobalScope is used here as an example, using it is highly discouraged, application code usually should use an application-defined CoroutineScope.
Given 2 or more flows with the same type, is there an existing Kotlin coroutine function to merge them, like the RX merge operator?
Currently I was considering this:
fun <T> merge(vararg flows: Flow<T>): Flow<T> = channelFlow {
val flowJobs = flows.map { flow ->
GlobalScope.launch { flow.collect { send(it) } }
}
flowJobs.joinAll()
}
but it seems somewhat clumsy.
This is now (Coroutines Version 1.3.5 at time of writing) part of the Coroutines library.
You use it like this:
val flowA = flow { emit(1) }
val flowB = flow { emit(2) }
merge(flowA, flowB).collect{ println(it) } // Prints two integers
// or:
listOf(flowA, flowB).merge().collect { println(it) } // Prints two integers
You can read more in the source code
I'm not too familiar with flows yet, so this might be suboptimal. Anyway, I think you could create a flow of all your input flows, and then use flattenMerge to flatten them into a single flow again. Something like this:
fun <T> merge(vararg flows: Flow<T>): Flow<T> = flowOf(*flows).flattenMerge()
Edit:
The merge-function was added to kotlinx-coroutines in the 1.3.3 release. See here: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/merge.html
It may be late but I believe this may be a viable solution:
fun <T> combineMerge(vararg flows: Flow<T>) = flow {
coroutineScope {
flows.forEach {
launch {
it.collect {
emit(it)
}
}
}
}
}
fun <T> combineConcat(vararg flows: Flow<T>) = flow {
flows.forEach {
it.collect {
emit(it)
}
}
}
I can't use "by lazy" because the callbacks require suspendCoroutine, which borks in android if it blocks the main thread, so I have to use the following "cache the result" pattern over and over. Is there a way to wrap it in a funButUseCachedResultsIfTheyAlreadyExist pattern to encapsulate the xCached object?
private var cameraDeviceCached: CameraDevice? = null
private suspend fun cameraDevice(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
... deep callbacks with cont.resume(camera) ...
}.also {
cameraDeviceCached = it
}
}
When what I'd really like to write is
private suspend fun cameraDevice(): CameraDevice = theMagicFunction { cont ->
... deep callbacks with cont.resume(camera) ...
}
You can build a generalized solution by wrapping an async call as follows:
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.LAZY
class LazySuspendFun<out T>(
scope: CoroutineScope,
private val block: suspend () -> T
) {
private val deferred = scope.async(Dispatchers.Unconfined, LAZY) { block() }
suspend operator fun invoke() = deferred.await()
}
fun <T> CoroutineScope.lazySuspendFun(block: suspend () -> T) =
LazySuspendFun(this, block)
This is a simple example of how you can use it. Note that we are able to compose them so that we use a lazy-inited value as a dependency to getting another one:
val fetchToken = lazySuspendFun<String> {
suspendCoroutine { continuation ->
Thread {
info { "Fetching token" }
sleep(3000)
info { "Got token" }
continuation.resume("hodda_")
}.start()
}
}
val fetchPosts = lazySuspendFun<List<String>> {
val token = fetchToken()
suspendCoroutine { continuation ->
Thread {
info { "Fetching posts" }
sleep(3000)
info { "Got posts" }
continuation.resume(listOf("${token}post1", "${token}post2"))
}
}
}
On the calling side you must be inside some coroutine context so you can call the suspending functions:
myScope.launch {
val posts = fetchPosts()
...
}
This solution is robust enough that you can concurrently request the value several times and the initializer will run only once.
I'll write this as an answer, since it's not possible to post much code in comments.
What you're looking for is something like this:
private suspend fun cameraDevice() = theMagicFunction {
CameraDevice()
}()
suspend fun theMagicFunction(block: ()->CameraDevice): () -> CameraDevice {
var cameraDeviceCached: CameraDevice? = null
return fun(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
cont.resume(block())
}.also {
cameraDeviceCached = it
}
}
}
Unfortunately, this will not compile, since closures cannot be suspendable, and neither are local functions.
Best I can suggest, unless I miss a solution there, is to encapsulate this in a class, if this variable bothers you too much.