I am trying to write a class to easily chain code run in different coroutine contexts.
Ideally, I would like to use it like this:
io {
// Run IO code that returns an object (nullable)
} ui { ioResult->
// Run UI code using the returned object (non-nullable)
} ifNull {
// Run UI code when the returned object is null
}
What I have so far works like this:
GlobalScope.launch {
CoroutineLinker(null).io {
// Run IO code
} ui { ioResult ->
ioResult?.also {
// Run UI code after null check
} ?: run {
// Run UI code when null
}
} ifNull {
// Redundant block
}
}
As you can see there is still quite some work left but I am stuck, so I share this with you:
class CoroutineLinker<T> (
private val value: T?
) {
suspend infix fun <K> io (block: suspend () -> K?): CoroutineLinker<K?> {
return withContext(Dispatchers.IO) {
CoroutineLinker(block())
}
}
suspend infix fun ui (block: suspend (value: T) -> Unit): CoroutineLinker<T> {
return withContext(Dispatchers.Main) {
if (value != null ) {
block(value)
}
this#CoroutineLinker
}
}
suspend infix fun ifNull (block: suspend () -> Unit) {
return withContext(Dispatchers.Main) {
if (value == null) {
block()
}
}
}
}
Any input is welcome! :)
I think this will do what you need:
suspend fun <K : Any> io (block: suspend () -> K?) = CoroutineLinker(null).io(block)
class CoroutineLinker<T : Any> (
private val value: T?
) {
suspend infix fun <K : Any> io (block: suspend () -> K?): CoroutineLinker<K> {
return withContext(Dispatchers.IO) {
CoroutineLinker(block())
}
}
suspend infix fun ui (block: suspend (value: T) -> Unit): CoroutineLinker<T> {
if (value != null ) {
withContext(Dispatchers.Main) {
block(value)
}
}
return this
}
suspend infix fun ifNull (block: suspend () -> Unit) {
if (value == null) {
withContext(Dispatchers.Main) {
block()
}
}
}
}
I changed 3 things:
Added upper bounds for CoroutineLinker to Any.
Added io function.
Changed the order of if and withContext in both functions - this is just optimization, it wasn't required.
Related
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 }
I do have the project that uses both coroutines and Vert.x.
I'm trying to write a wrapper function to run blocking code on vertx worker thread pool
Something like:
suspend inline fun <T> executeOnWorkerThread(crossinline block: () -> T) =
withContext(**Vertx-Worker-ThreadPool**) {
block()
}
So it may be used like
suspend fun usage(obj: Any): String = executeOnWorkerThread {
try {
// blocking code
} catch (e: Exception) {
// Exception handling
}
}
But this is not vert.x way. And I couldn't find the way to extract thread pool out of vert.x
suspend fun <T> awaitBlockingUnordered(block: () -> T): T {
return awaitResult { handler ->
val ctx = Vertx.currentContext()
ctx.executeBlocking<T>(
{ fut -> fut.complete(block()) },
false,
{ ar -> handler.handle(ar) }
)
}
}
I wrote this helper function, so that I can easily process a list in parallel and only continue code execution when all the work is done. It works nicely when you don't need to return a result.
(I know it isn't the best practice to create new pools every time, it can be easily moved out, but I wanted to keep the examples simple.)
fun recursiveAction(action: () -> Unit): RecursiveAction {
return object : RecursiveAction() {
override fun compute() {
action()
}
}
}
fun <T> List<T>.parallelForEach(parallelSize: Int, action: (T) -> Unit) {
ForkJoinPool(parallelSize).invoke(recursiveAction {
this.parallelStream().forEach { action(it) }
})
}
Example use:
val myList: List<SomeClass> [...]
val parallelSize: Int = 8
myList.parallelForEach(parallelSize) { listElement ->
//Some task here
}
Is there any way to make a similar helper construct for when you want to collect the results back into a list?
I know I have to use a RecursiveTask instead of the RecursiveAction, but I couldn't manage to write a helper function like I had above to wrap it.
I'd like to use it like this:
val myList: List<SomeClass> [...]
val parallelSize: Int = 8
val result: List<SomeClass> = myList.parallelForEach(parallelSize) { listElement ->
//Some task here
}
Alternatively, is there a simpler way to do this alltogether?
Answered by JeffMurdock over on Reddit
fun <T> recursiveTask(action: () -> T): RecursiveTask<T> {
return object : RecursiveTask<T>() {
override fun compute(): T {
return action()
}
}
}
fun <T, E> List<T>.parallelForEach(parallelSize: Int, action: (T) -> E): List<E> {
val pool = ForkJoinPool(parallelSize)
val result = mutableListOf<ForkJoinTask<E>>()
for (item in this) {
result.add(pool.submit(recursiveTask {
action(item)
}))
}
return result.map { it.join() }
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
list.parallelForEach(3) { it + 2 }.forEach { println(it) }
}
I'm trying to implement a parallel implementation for both Iterable and Sequence in Kotlin. I got a little file, it consists of 4 extension functions, but the third one gives me an compiler error:
suspend fun <T, R> Iterable<T>.parallelMap(block: suspend(T) -> R) =
coroutineScope { map { async { block(it) } }.map { it.await() } }
suspend fun <T> Iterable<T>.parallelForEach(block: suspend (T) -> Unit) =
coroutineScope { map { async { block(it) } }.forEach { it.await() } }
suspend fun <T, R> Sequence<T>.parallelMap(block: suspend(T) -> R) =
coroutineScope { map { async { block(it) } }.map { it.await() } }
suspend fun <T> Sequence<T>.parallelForEach(block: suspend (T) -> Unit) =
coroutineScope { map { async { block(it) } }.forEach { it.await() } }
The compiler comes back and says that suspension functions can only be called inside suspension functions. Is there a way to implement this?
Edit: fixed bad copy/paste
Edit2: I thought of an implementation:
suspend fun <T, R> Sequence<T>.parrallelMap(block: suspend (T) -> R) =
asIterable().map { coroutineScope { async { block(it) } } }
.asSequence().map { runBlocking { it.await() } }
I was hoping that this would fire all the suspending functions and await them lazily. I'm just not sure if this is safe, or this saves time or not.
There is a problem with the core semantics of parallel execution for lazy sequences. Your current implementation does not start block(it) until the resulting sequence is iterated:
suspend fun <T, R> Sequence<T>.parallelMap(block: suspend(T) -> R) =
coroutineScope { map { async { block(it) } }.map { it.await() } }
Consider the following example:
sequenceOf(1, 2, 3).parallelMap { it * it }.forEach { println(it) }
For this sample the order of execution will be
val p1 = async { 1 * 1 }
val r1 = p1.await()
println(r1)
val p2 = async { 2 * 2 }
val r2 = p2.await()
println(r2)
val p3 = async { 3 * 3 }
val r3 = p3.await()
println(r3)
Note that the execution of the mapping operations is sequientional, not parallel.
What the compiler tells you is that the lambda of Sequence<T>.map {} is performed lazily on-demand outside of the context of the call (read: outside of your coroutine), so you can't use the coroutine you are currently in.
Frankly, I am not sure how one can both perform lazy computation and do it in parallel.
What I want is a function like this:
suspendCoroutineWithTimeout(timeout: Long, unit: TimeUnit, crossinline block: (Continuation<T>) -> Unit)
That does basically the same thing as the existing suspendCoroutine function but if the callback or whatever was provided in the block dosen't get called within the specified timeout the corutine continues but with a TimeoutException or something like that.
You can combine withTimeout and suspendCancellableCoroutine in a straightforward way for the desired effect:
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long, unit: TimeUnit,
crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout, unit) {
suspendCancellableCoroutine(block = block)
}
Perfect answer from #Roman Elizarov.. Just adding my 2 cents on it because I needed a return from that call.. So adding T? return it would be ...
suspend inline fun <T> suspendCoroutineWithTimeout(timeout: Long, crossinline block: (Continuation<T>) -> Unit ) : T? {
var finalValue : T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
If you're using suspendCoroutine, that means you have full control over what you do with the continuation you got. For example, you can pass it to the callback-based async API and, additionally, to a scheduled task that will resume it with exception:
suspend fun mySuspendFun(timeout: Long): String {
val didResume = AtomicBoolean()
fun markResumed() = !didResume.getAndSet(true)
return suspendCoroutine { cont ->
launch(CommonPool) {
delay(timeout)
if (markResumed()) {
cont.resumeWithException(TimeoutException())
}
}
// call Async API, and in the callback, use
// if (markResumed()) {
// cont.resume(result)
// }
}
}
However, Kotlin's standard library supports your use case first-class, as described in Roman Elizarov's answer. I suggest you use that approach in your project.
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long,
crossinline block: (CancellableContinuation<T>) -> Unit
): T? {
var finalValue: T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
suspend inline fun <T> suspendCoroutineObserverWithTimeout(
timeout: Long,
data: LiveData<T>,
crossinline block: (T) -> Boolean
): T? {
return suspendCoroutineWithTimeout<T>(timeout) { suspend ->
var observers : Observer<T>? = null
val oldData = data.value
observers = Observer<T> { t ->
if (oldData == t) {
KLog.e("参数一样,直接return")
return#Observer
}
KLog.e("参数不一样,刷新一波")
if (block(t) && !suspend.isCancelled) {
suspend.resume(t)
observers?.let { data.removeObserver(it) }
}
}
data.observeForever(observers)
suspend.invokeOnCancellation {
KLog.e("删除observiers")
observers.let { data.removeObserver(it) }
}
}
}
The previous #Roman Elizarov and #febaisi answers have been answered very well, I added a type judgment and livedata on this basis, and I will return only when the conditions are met. Sorry, my English is not very good. –