in the following code:
private fun executeCognitoRequest(result: MethodChannel.Result, block: suspend CoroutineScope.() -> Any?) {
try {
CoroutineScope(Dispatchers.Default).launch {
val requestResult = block()
withContext(Dispatchers.Main) {
result.success(requestResult)
}
}
} catch (exception: Exception) {
val cognitoErrorType = CognitoErrorType.getByException(exception)
result.error(cognitoErrorType.code, null, null)
}
}
if the call to block throws, will it be caught?
It will be caught, but the problem with your code is that you violate the principles of structured concurrency and launch a coroutine in the GlobalScope. So if you test your code from a main function like this:
fun main() {
runBlocking {
executeCognitoRequest(MethodChannel.Result()) {
funThatThrows()
}
}
}
the whole program will end before the coroutine has completed execution.
This is how you should write your function:
private fun CoroutineScope.executeCognitoRequest(
result: MethodChannel.Result,
block: suspend CoroutineScope.() -> Any?
) {
try {
launch(Dispatchers.IO) {
val requestResult = block()
withContext(Dispatchers.Main) {
result.success(requestResult)
}
}
} catch (exception: Exception) {
val cognitoErrorType = CognitoErrorType.getByException(exception)
result.error(cognitoErrorType.code, null, null)
}
}
Now your function is an extension on CoroutineScope and launch is automatically called with that receiver. Also, for blocking IO calls you shouldn't use the Default but the IO dispatcher.
However, I find your higher-level design weird, you start from blocking code and turn it into async, callback-oriented code. Coroutines are there to help you get rid of callbacks.
Related
When we have a coroutine scope, when it is canceled, can it be used again?
e.g. for the below, when I have scope.cancel, the scope.launch no longer work
#Test
fun testingLaunch() {
val scope = MainScope()
runBlocking {
scope.cancel()
scope.launch {
try {
println("Start Launch 2")
delay(200)
println("End Launch 2")
} catch (e: CancellationException) {
println("Cancellation Exception")
}
}.join()
println("Finished")
}
}
Similarly, when we have scope.cancel before await called,
#Test
fun testingAsync() {
val scope = MainScope()
runBlocking {
scope.cancel()
val defer = scope.async {
try {
println("Start Launch 2")
delay(200)
println("End Launch 2")
} catch (e: CancellationException) {
println("Cancellation Exception")
}
}
defer.await()
println("Finished")
}
}
It will not execute. Instead, it will crash with
kotlinx.coroutines.JobCancellationException: Job was cancelled
; job=SupervisorJobImpl{Cancelled}#39529185
at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1579)
at kotlinx.coroutines.CoroutineScopeKt.cancel(CoroutineScope.kt:217)
at kotlinx.coroutines.CoroutineScopeKt.cancel$default(CoroutineScope.kt:215)
at com.example.coroutinerevise.CoroutineExperiment$testingAsync$1.invokeSuspend(CoroutineExperiment.kt:241)
at |b|b|b(Coroutine boundary.|b(|b)
at kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:101)
at com.example.coroutinerevise.CoroutineExperiment$testingAsync$1.invokeSuspend(CoroutineExperiment.kt:254)
Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelled}#39529185
at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1579)
at kotlinx.coroutines.CoroutineScopeKt.cancel(CoroutineScope.kt:217)
at kotlinx.coroutines.CoroutineScopeKt.cancel$default(CoroutineScope.kt:215)
at com.example.coroutinerevise.CoroutineExperiment$testingAsync$1.invokeSuspend(CoroutineExperiment.kt:241)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Is it true, a canceled coroutine scope cannot be used for launch or async anymore?
Instead of using CoroutineScope for cancelling all of the launched jobs in it, you may want to use the underlying CoroutineContext with its cancelChildren() method which doesn't affect the Job state (which is not true for plain cancel() method) and allows to continue launching new coroutines after being invoked.
Following up on #Alex Bonel response.
For example you have a method like
fun doApiCall() {
viewModelScope.launch {
// do api call here
}
}
You can call doApiCall() again and again
viewModelScope.coroutineContext.cancelChildren()
doApiCall()
Calling doApiCall() will not have any effect.
viewModelScope.coroutineContext.cancel()
doApiCall() // would not call
I am using that in compose and it is slightly different.
Define coroutine scope in compose
val coroutineScope = rememberCoroutineScope()
Launch coroutine
coroutineScope.launch(Dispatchers.Main) { //Perform your operation }
Cancel all coroutines that belong to coroutineScope
coroutineScope.coroutineContext.cancelChildren()
Related to lifecycleScope comment above, I'm not sure how common cancellation like this would be in practice? fwiw something like following will work:
val scope = MainScope()
runBlocking {
val job1 = scope.launch {
try {
println("Start Launch 1")
delay(200)
println("End Launch 1")
} catch (e: CancellationException) {
println("Cancellation Exception")
}
}
job1.cancel()
val job2 = scope.launch {
try {
println("Start Launch 2")
delay(200)
println("End Launch 2")
} catch (e: CancellationException) {
println("Cancellation Exception")
}
}
job2.join()
println("Finished")
}
This particular example will print
Start Launch 1
Cancellation Exception
Start Launch 2
End Launch 2
Finished
I needed to collect the event just once and then stop listening. I came up with this solution:
/**
* Collect the value once and stop listening (one-time events)
*/
suspend fun <T> Flow<T>.collectOnce(action: suspend (value: T) -> Unit) {
try {
coroutineScope {
collectLatest {
action(it)
this#coroutineScope.cancel()
}
}
} catch (e: CancellationException) { }
}
You can use it like this:
viewModelScope.launch {
myStateFlow.collectOnce {
// Code to run
}
}
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'm trying to execute some code after calling collect on a Flow<MyClass>. I'm still kind of new to using Flows so I don't understand why the code after the function doesn't get called.
How I use the Flow:
incidentListener = FirebaseUtils.databaseReference
.child(AppConstants.FIREBASE_PATH_AS)
.child(id)
.listen<MyClass>() //This returns a Flow<MyClass?>?
How I consume the Flow:
private suspend fun myFun() {
viewmodel.getListener()?.collect { myClass->
//do something here
}
withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}
How myFun() is called:
CoroutineScope(Dispatchers.IO).launch {
myFun()
}
As far as what I've tried to make it work I've tried closing the coroutine context and it didn't work. I'm assuming Flows work differently than regular coroutines.
Update:
I'm listening through Firebase using this block of code. I don't know if it'll help but maybe the way I implemented it is causing the issue?
inline fun <reified T> Query.listen(): Flow<T?>? =
callbackFlow {
val valueListener = object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
close()
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(value)
} catch (exp: Exception) {
if (!isClosedForSend) offer(null)
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
collect is a suspending function, the code after collect will only run once the flow completes.
Launch it in a separate coroutine:
private suspend fun myFun() {
coroutineScope {
launch {
viewmodel.getListener()?.collect { myClass->
//do something here
}
}
withContext(Dispatchers.Main) { updateUI() } //the code never reaches this part
}
}
I forgot to post my own answer to this. I've found the problem before. It's because I wasn't returning the Coroutine Context.
My code has been updated since but with the code above as an example it should be written as follows:
private suspend fun myFun() {
viewmodel.getListener()?.collect { myClass->
//do something here
return#collect
}
withContext(Dispatchers.Main) { return#withContext updateUI() }
//the code should flow downwards as usual
}
I am writing a custom loop dsl and I want it's usage to look like below
var counter1 = 0
var counter2 = 0
loop {
counter1 += 1
println(counter1)
stopIf(counter1 == 5) // loop should terminate here and not execute rest of the code if condition matches
counter2 += 2
println(counter2)
stopIf(counter2 == 8) // loop should terminate here and not execute rest of the code if condition matches
}
I have following code which does allows me to write stopIf any number of times and anywhere in the loop body but when condition matches it does not terminate immediately but executes rest of the loop body and then terminates.
#UseExperimental(ExperimentalTime::class)
open class Loop {
var stop = false
val loopInterval = 1.seconds
suspend fun loop(block: suspend () -> Unit): Unit = loop(loopInterval, block)
suspend fun loop(minimumInterval: Duration, block: suspend () -> Unit): Unit =
loopWithoutDelay { delayedResult(maxOf(minimumInterval, loopInterval), block) }
private suspend fun loopWithoutDelay(block: suspend () -> Unit) {
block()
if (stop) return else loopWithoutDelay(block)
}
suspend fun <T> delayedResult(minDelay: Duration, f: suspend () -> T): T = coroutineScope {
val futureValue = async { f() }
delay(minDelay.toJavaDuration())
futureValue.await()
}
fun stopIf(condition: Boolean) {
if (condition) {
stop = condition // once stop condition matches, then do not override it with following false condtions
}
}
}
#ExperimentalTime
suspend fun loop(block: suspend Loop.() -> Unit) =
Loop().run { loop { block(this) } }
I have tried to use return with label but it did not work. Is there any way I can achieve this?
It can be done for example with throwing a lightweight exception. You have to declare custom exception:
class LoopStopException : Throwable("Stop look", null, false, false) // lightweight throwable without the stack trace
and catch it in loopWithoutDelay:
private suspend fun loopWithoutDelay(block: suspend () -> Unit) {
try {
while (true) {
block()
}
} catch (e: LoopStopException) {
//do nothing
}
}
I didn't understand much about the function delayedResult, because none of the dsl's public functions return a result. However, I come up with an solution for cancelling the loop.
As far as I understood, we have to have a loop that doesn't block the current thread. Therefore, it must be run in a coroutine, but in order to be able to cancel the loop, the dsl must run its own coroutine. This inner coroutine is run using coroutineScope, so it suspends the parent coroutine until it's finished or cancelled.
#ExperimentalTime
class Loop {
private val loopInterval = 1.seconds
suspend fun loop(block: suspend () -> Unit) = loop(loopInterval, block)
suspend fun loop(minimumInterval: Duration, block: suspend () -> Unit):Job = coroutineScope {
launch {
while (true) {
block()
delay(minOf(minimumInterval, loopInterval).toLongMilliseconds())
}
}
}
suspend fun stopIf(condition: Boolean) = coroutineScope {
suspendCancellableCoroutine<Unit> {
if (condition) it.cancel() else it.resumeWith(Result.success(Unit))
}
}
}
#ExperimentalTime
suspend fun loop(block: suspend Loop.() -> Unit):Job {
return Loop().run {
this.loop {
block(this)
}
}
}
I want to execute multiple jobs in parallel using coroutines. This is the piece of code that I came up with.
I have 2 queries:
How do I ensure completion callbacks happen in the caller thread?
Code has become more like the callback pattern I used to have with
normal threads. Please suggest changes in design to achieve the
coroutines readability advantage.
class ParallelExecutor {
suspend fun <OUTPUT> execute(
jobs: List<suspend () -> OUTPUT>,
onTimeout: (jobIndex: Int) -> OUTPUT,
onFailure: (jobIndex: Int, exception: Throwable) -> OUTPUT,
onCompletion: suspend (jobIndex: Int, result: OUTPUT) -> Unit,
timeout: Long,
onFullCompletion: suspend () -> Unit = {},
invokeDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
withContext(invokeDispatcher) {
var counter = 0
val listenJobs = mutableListOf<Deferred<OUTPUT>>()
jobs.forEachIndexed { index, job ->
val listenJob = async {
try {
job()
} catch (e: Exception) {
onFailure(index, e)
}
}
listenJobs.add(listenJob)
}
listenJobs.forEachIndexed { index, job ->
launch {
val output = try {
withTimeout(timeout) {
job.await()
}
} catch (e: TimeoutCancellationException) {
onTimeout(index)
}
onCompletion(index, output)
if (++counter == listenJobs.size) {
onFullCompletion()
}
}
}
}
}
}
It seems to me that you can simplify your code quite a bit. You don't need a two-step idiom that first launches all the async jobs and then launches more jobs to await on them. You can just launch the jobs and delegate to the callbacks within the same block. That way the callbacks will naturally be invoked on the caller's dispatcher and only the job itself can be called within the changed context with the invokeDispatcher.
onFullCompletion looks like a piece of code that belongs on the caller side, below the execute call. Since execute doesn't throw any exceptions, you don't need any try-finally to get it.
suspend fun <OUTPUT> execute(
jobs: List<suspend () -> OUTPUT>,
onTimeout: (jobIndex: Int) -> OUTPUT,
onFailure: (jobIndex: Int, exception: Throwable) -> OUTPUT,
onCompletion: suspend (jobIndex: Int, result: OUTPUT) -> Unit,
timeout: Long,
invokeDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
coroutineScope {
jobs.mapIndexed { index, job ->
launch {
val output = try {
withTimeout(timeout) {
withContext(invokeDispatcher) {
job()
}
}
} catch (e: TimeoutCancellationException) {
onTimeout(index)
} catch (e: Exception) {
onFailure(index, e)
}
onCompletion(index, output)
}
}
}
}
Made some improvements that should answer your queries.
class ParallelExecutor {
suspend fun <OUTPUT> execute(
jobs: List<suspend () -> OUTPUT>,
onTimeout: (jobIndex: Int) -> OUTPUT,
onFailure: (jobIndex: Int, exception: Throwable) -> OUTPUT,
onCompletion: suspend (jobIndex: Int, result: OUTPUT) -> Unit,
timeout: Long,
invokeDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
supervisorScope {
val listenJobs = jobs.map { job ->
async(invokeDispatcher) {
withTimeout(timeout) {
job()
}
}
}
listenJobs.forEachIndexed { index, job ->
launch {
val output = try {
job.await()
} catch (e: TimeoutCancellationException) {
onTimeout(index)
} catch (e: Exception) {
onFailure(index, e)
}
onCompletion(index, output)
}
}
}
}
}
Jobs are now cancelled when timeout is reached.
Completion callbacks are now called in the caller's dispatcher.
The race condition when deciding when to call onFullCompletion has been fixed.
Removed some methods that you didn't really need.
If you feel this is more like the callback pattern, then you simply shouldn't use callbacks. Coroutines are designed such that you write this sort of code at use site with minimal boilerplate, so functions like this aren't necessary and look weird (IMHO).