Wrapping async code with suspendCoroutine vs sync code with suspending builder - kotlin

I'm looking for suspending way writing to file. I found this example from Kotlin/coroutines-examples which is wraping AsynchronousFileChannel with suspendCoroutine { ... } but I'm wondering if there is any benefit from wrapping synchronous call with withContext(IO){}
private suspend fun File.writeTextAsync(text: String): Unit = suspendCoroutine { cont ->
val aFileChannel = AsynchronousFileChannel.open(toPath(), StandardOpenOption.WRITE)
val byteBuffer = ByteBuffer.wrap(text.toByteArray())
aFileChannel.write(
byteBuffer,
0,
Unit,
object : java.nio.channels.CompletionHandler<Int, Unit> {
override fun completed(bytesRead: Int, attachment: Unit) {
cont.resume(Unit)
}
override fun failed(exception: Throwable, attachment: Unit) {
cont.resumeWithException(exception)
}
})
}
vs.
withContext(IO) { File(...).writeText(text) }

Related

Builder pattern with infix for Coroutines

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.

Launch multiple suspend fun

I created a TextWatcher extension to listen to text updates with debounce. I wrapped the TextWatcher into a callbackFlow which offers the text input through Flow.
The issue is that calling collect() suspends the processing and I need to register it for multiple EditText. Is calling multiple launch inside the scope the right way correct?
private fun initListeners() = lifecycleScope.launch {
launch {
edittext_taskdetail_title.textChangedFlow()
.collect { text -> viewModel.updateTitle(text) }
}
launch {
edittext_taskdetail_description.textChangedFlow()
.collect { text -> viewModel.updateDescription(text) }
}
}
This is the extension:
fun TextView.textChangedFlow(): Flow<String> {
val flow: Flow<String> = callbackFlow {
val listener = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
offer(s.toString())
}
}
addTextChangedListener(listener)
awaitClose { removeTextChangedListener(listener) }
}
return flow.debounce(TEXT_UPDATE_DEBOUNCE)
}
What you have is acceptable I think but here's an alternative.
private fun initListeners() {
edittext_taskdetail_title.textChangedFlow()
.onEach { text -> viewModel.updateTitle(text) }
.launchIn(lifecycleScope)
edittext_taskdetail_description.textChangedFlow()
.onEach { text -> viewModel.updateDescription(text) }
.launchIn(lifecycleScope)
}
I does pretty much the same thing except, if one of them fails for some reason, the other one won't be cancelled immediately.

How to implement parallel mapping for sequences in kotlin

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.

Is it possible to suspend a coroutine with a timeout?

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. –

Can't reify a suspendable block passed to forEach

Given some
suspend fun a(): Int
This works:
launch(Unconfined) {
(1..10).forEach {
val a = a()
println("Result is $a")
}
}
But this fails at compile time:
val action: (Int) -> Unit = {
// Suspend function should be called only from a coroutine
// or another suspend function:
val a = a()
println("Result is $a")
}
launch(Unconfined) {
(1..10).forEach(action)
}
Furthermore, it isn't fixable because:
val action: suspend (Int) -> Unit = {
val a = a()
println("Result is $a")
}
launch(Unconfined) {
// suspend (Int) -> Unit cannot be applied to (T) -> Unit
(1..10).forEach(action)
}
What is the story here in terms of the static type system? The current situation looks like a quick hack where an inline block containing a suspend fun call is still inferred to a non-suspend type signature.
Is this an area where the design will be improved before being finalized?
The suspend and normal functional types are not subtypes of each other and thus cannot be assigned or passed to a function in place of each other:
val f: () -> Unit = { }
val g: suspend () -> Unit = f // Type mismatch
val f: suspend () -> Unit = { }
val g: () -> Unit = f // Type mismatch
This is why a suspend (Int) -> Unit cannot be passed to forEach.
Basically, the restriction for suspend functions to be called only in other suspend functions works irrespective to the type system. Such calls should simply be placed inside a suspend function or a suspend lambda or inlined into one. So, this should also work:
val action: suspend (Int) -> Unit = {
val a = a()
println("Result is $a")
}
launch(Unconfined) {
(1..10).forEach { action() } // The call is inlined into a suspend lambda
}
I've filed an issue about supporting (1..10).forEach(action) as well: KT-22186