Trying to grasp coroutines. I have an expectation that this code shouldn't print anything. However, it prints "work done" so cancellation didn't do a thing. How is it so?
suspend fun foo() = coroutineScope {
launch { doSomeWork() }
}
suspend fun doSomeWork() {
delay(10000)
println("Work done")
}
suspend fun main() {
val fooResult = foo()
fooResult.cancel()
}
I finally got it. The main coroutine suspends on "coroutineScope" call, that's why
Related
Hi am trying to run following kotlin code:
Code Snippet 1
This is not working
with the error:
function 'delay' should be called only from a coroutine or another suspend function
package kotlinx.coroutines.guide.exampleBasic02
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch {echoWorldWrapper() }
println("Hello")
}
suspend fun echoWorldWrapper() {
echoWorld()
}
fun echoWorld() {
delay(1000L)
println("World!")
}
However following works:
Code Snippet 2:
package kotlinx.coroutines.guide.exampleBasic02
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch {echoWorldWrapper() }
println("Hello")
}
suspend fun echoWorldWrapper() {
delay(1000L)
println("World!")
}
In my production code I can only follow Code Snippet 1. Is there any workaround for approach 1 to work. I am new to Kotlin and couldn't find any other answer to this question. Thanks.
It is not possible to call a suspend function from a function that is not also a suspend function. suspend carries with it under the hood the state of a coroutine in a Continuation object. delay() cannot operate without that coroutine to work with.
The functionality of delay() is to suspend a coroutine for a period of time and then resume it. It doesn't make any sense for it to be called from a non-suspend function, because that means there is no coroutine to suspend.
The alternative for a non-suspend function is to use Thread.sleep(), which will block the thread for a period of time. Example:
//Use withContext to make it acceptable to call a blocking function in a coroutine
suspend fun echoWorldWrapper() = withContext(Dispatchers.IO) {
echoWorld()
}
// A blocking function:
fun echoWorld() {
Thread.sleep(1000L)
println("World!")
}
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.
I have following code:
Timber.d("Calling coroutine from thread: ${Thread.currentThread().name}")
scope.launch {
Timber.d("Current thread: ${Thread.currentThread().name}")
runTest()
}
suspend fun runTest() {
coroutineScope {
launch(Dispatchers.Main) {
Timber.d("Running from thread: ${Thread.currentThread().name}")
}
}
}
If I run it. App crashes with no error in log.
In my log I see:
Calling coroutine from thread: main
Current thread: DefaultDispatcher-worker-2
But I don't see entry with Running from thread:
This is done in viewmodel
My scope looks like this:
val scope: ViewModelCoroutineScope = ViewModelCoroutineScope(Dispatchers.Default)
class ViewModelCoroutineScope(
context: CoroutineContext
) : CoroutineScope {
private var onViewDetachJob = Job()
override val coroutineContext: CoroutineContext = context + onViewDetachJob
fun onCleared() {
onViewDetachJob.cancel()
}
}
What am I doing wrong?
Moving to a different machine I finally got some errors about Dispatchers.MAIN not working.
In the end all I had to do was replace all raw coroutine dependencies with:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1")
I'm trying to create an object which can execute some tasks sequentially in its own thread like it is a queue.
The following sample is just for demonstrating my setup and may be completely wrong.
class CoroutinesTest {
fun a() {
GlobalScope.launch {
println("a started")
delay(1000)
println("a completed")
}
}
fun b() {
GlobalScope.launch {
println("b started")
delay(2000)
println("b completed")
}
}
fun complex() {
a()
b()
}
}
fun main() {
runBlocking {
val coroutinesTest = CoroutinesTest()
coroutinesTest.complex()
delay(10000)
}
}
For now this code prints the following
a started
b started
a completed
b completed
which means a and b executed in parallel. Methods a, b and complex can be called from different threads. Of course, the complex method should also support this concept. For now, I need a mechanism that allows me to execute only one task at a moment, so I could get the following output:
a started
a completed
b started
b completed
I did some research and think that actor with a Channel can do what needed, but actor for now is marked as obsolete (issue #87). I don't like the idea of using API that is subject to change, so I would like to do the thing in a common way.
TL;DR There are a few options for controlling sequential coroutines.
Use a Channel to make them run one at a time in the order called
Use a Mutex to make them run one at a time but without a guarantee of order
Use a Flow (as described in the answer below by BigSt) to make them run one at a time in the order called, however make sure that the flow buffer is large enough or jobs can be lost if the number of jobs "in flight" is larger than the buffer size.
If the desired sequence is always the same, put the actual work into suspend functions and call the sequence from within the same coroutine scope to make them run one at a time in the order prescribed by the code
Channel
One way to control execution order is to use a Channel - where lazily executed coroutine jobs are passed to the channel to be run in sequence. Unlike the Mutex, the Channel guarantees that the jobs are run in the order they are launched.
class CoroutinesTest {
private val channel = Channel<Job>(capacity = Channel.UNLIMITED).apply {
GlobalScope.launch {
consumeEach { it.join() }
}
}
fun a() {
channel.trySend(
GlobalScope.launch(start = CoroutineStart.LAZY) {
println("a started")
delay(1000)
println("a completed")
}
)
}
fun b() {
channel.trySend(
GlobalScope.launch(start = CoroutineStart.LAZY) {
println("b started")
delay(2000)
println("b completed")
}
)
}
fun complex() {
// add two separate jobs to the channel,
// this will run a, then b
a()
b()
}
}
Calling complex always produces:
a started
a completed
b started
b completed
Mutex
You can keep jobs from running at the same time with a Mutex and withLock call. The call order is not guaranteed if you make a bunch of calls in short succession. For example:
class CoroutinesTest {
private val lock = Mutex()
fun a() {
GlobalScope.launch {
lock.withLock {
println("a started")
delay(1000)
println("a completed")
}
}
}
fun b() {
GlobalScope.launch {
lock.withLock {
println("b started")
delay(2000)
println("b completed")
}
}
}
fun complex() {
a()
b()
}
}
Calling complex can produce:
a started
a completed
b started
b completed
or:
b started
b completed
a started
a completed
Suspend Functions
If you must always run a then b you can make both of them suspend functions and call them from within a single scope (only allowing the complex call, not individual a and b calls). In this case, the complex call does guarantee that a runs and completes before starting b.
class CoroutinesTest {
suspend fun aImpl() {
println("a started")
delay(1000)
println("a completed")
}
suspend fun bImpl() {
println("b started")
delay(2000)
println("b completed")
}
fun complex() {
GlobalScope.launch {
aImpl()
bImpl()
}
}
}
Calling complex always produces:
a started
a completed
b started
b completed
Old question but here's a simpler approach anyway.
Change a() to return the Coroutine job:
fun a() = GlobalScope.launch {
println("a started")
delay(1000)
println("a completed")
}
Then you can invoke a() / b() like this:
a().invokeOnCompletion { b() }
This way b() won't be triggered before a() terminates.
Alternatively you can use join:
fun complex() {
GlobalScope.launch {
a().join()
b()
}
}
Flows are sequential, using MutableSharedFlow it can be achieved like the following:
class CoroutinesTest {
// make sure replay(in case some jobs were emitted before sharedFlow is being collected and could be lost)
// and extraBufferCapacity are large enough to handle all the jobs.
// In case some jobs are lost try to increase either of the values.
private val sharedFlow = MutableSharedFlow<Job>(replay = 2, extraBufferCapacity = 2)
init {
sharedFlow.onEach { job ->
job.join()
}.launchIn(GlobalScope)
}
fun a() {
// emit job to the Flow to execute sequentially
sharedFlow.tryEmit(
// using CoroutineStart.LAZY here to start a coroutine when join() is called
GlobalScope.launch(start = CoroutineStart.LAZY) {
println("a started")
delay(1000)
println("a completed")
}
)
}
fun b() {
// emit job to the Flow to execute sequentially
sharedFlow.tryEmit(
// using CoroutineStart.LAZY here to start a coroutine when join() is called
GlobalScope.launch(start = CoroutineStart.LAZY) {
println("b started")
delay(2000)
println("b completed")
}
)
}
fun complex() {
a()
b()
}
}
Note: GlobalScope is not recommended to use, it violates the principle of structured concurrency.
How can I launch a coroutine from a suspend function and have it use the current Scope? (so that the Scope doesn't end until the launched coroutine also ends)
I'd like to write something like the following –
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
go()
}
suspend fun go() {
launch {
println("go!")
}
}
But this has a syntax error: "Unresolved Reference: launch". It seems launch must be run in one of the following ways –
GlobalScope.launch {
println("Go!")
}
Or
runBlocking {
launch {
println("Go!")
}
}
Or
withContext(Dispatchers.Default) {
launch {
println("Go!")
}
}
Or
coroutineScope {
launch {
println("Go!")
}
}
None of these alternatives does what I need. Either the code "blocks" instead of "spawning", or it spawns but the parent scope won't wait for its completion before the parent scope itself ends.
I need it to "spawn" (launch) in the current parent coroutine scope, and that parent scope should wait for the spawned coroutine to finish before it ends itself.
I expected that a simple launch inside a suspend fun would be valid and use its parent scope.
I'm using Kotlin 1.3 and cotlinx-coroutines-core:1.0.1.
You should make the function go an extension function of CoroutineScope:
fun main() = runBlocking {
go()
go()
go()
println("End")
}
fun CoroutineScope.go() = launch {
println("go!")
}
Read this article to understand why it is not a good idea to start in a suspend functions other coroutines without creating a new coroutineScope{}.
The convention is: In a suspend functions call other suspend functions and create a new CoroutineScope, if you need to start parallel coroutines. The result is, that the coroutine will only return, when all newly started coroutines have finished (structured concurrency).
On the other side, if you need to start new coroutines without knowing the scope, You create an extensions function of CoroutineScope, which itself it not suspendable. Now the caller can decide which scope should be used.
I believe I found a solution, which is with(CoroutineScope(coroutineContext). The following example illustrates this –
import kotlinx.coroutines.*
fun main() = runBlocking {
go()
go()
go()
println("End")
}
suspend fun go() {
// GlobalScope.launch { // spawns, but doesn't use parent scope
// runBlocking { // blocks
// withContext(Dispatchers.Default) { // blocks
// coroutineScope { // blocks
with(CoroutineScope(coroutineContext)) { // spawns and uses parent scope!
launch {
delay(2000L)
println("Go!")
}
}
}
However, Rene posted a much better solution above.
Say you are dealing with some RxJava Observable and it isn't the time to refactor them, you can now get a hold of a suspend function's CoroutineScope this way:
suspend fun yourExtraordinarySuspendFunction() = coroutineScope {
val innerScope = this // i.e. coroutineScope
legacyRxJavaUggh.subscribe { somePayloadFromRxJava ->
innerScope.launch {
// TODO your extraordinary work
}
}
}