2
votes

I am relatively new to Co-Routines and I am trying get the behaviour what the Launch co-routine would accomplish :

launch(UI) { 

     val v1 = someDeferredType 
     val v2 = v1.await()
     val v3 = v2.text

} 

In the above example v3 will wait for v2 to execute and then run while not blocking the main thread. While this is great, this brings in the Deferred Type and Co-routine logic in my Calling Activity/Fragment.

I would like to keep my Activity/Fragment free from specific implementation details, something like this :

 fun getResponseString() : String {

     launch(UI) { 

           val v1 = someDeferredType 
           val v2 = v1.await()
           val v3 = v2.text

      } 

      return v3 //This is the actual String which I need to be returned
 }

So that I can just call getResponseString() like a regular function from my activity.

The only option I have come across so far is to use runBlocking co-routine but thats blocks the main thread altogether unlike launch.

Maybe I am missing something or is it not possible to do something like this using Co-routines in Kotlin ?

2
You can't suspend any calls running on the main thread, You need to get by with a callback interface.Pawel
@Pawel - So essentially, I will either have to write launch in my UI code or fallback to an interface callback mechanism, right ?Adnan Mulla
The difference between regular and suspending function is not just formal or an implementation detail: it changes the semantics of your program. With sync code, you know all the operations it does are performed before any other UI event handlers are called. You lose that atomicity with async code and enter the world of "async hell" where your event handlers are running concurrently to each other.Marko Topolnik

2 Answers

3
votes

You cannot return result of an asynchronous operation from a regular function like getResponseString. Regular functions don't have this capability of suspending execution without blocking the thread they have been invoked on. That is why Kotlin has to introduce a concept of "suspending function", so you can write:

suspend fun getResponseString(): String {
    val v1 = someDeferredType 
    val v2 = v1.await()
    val v3 = v2.text
    return v3
}

The idea that you add suspend modifier to all your asynchronous functions (functions that has to wait for something but should not block UI thread) and then use launch(UI) { ... } only at the very top level where you need to initiate some self-contained asynchronous operation.

P.S. Also coroutines are spelled "coroutines". It is one word and there is no dash. See wikipedia, for example.

0
votes

The difference between a regular and a suspending function is not just an implementation detail: it changes the semantics of your program. With sync code, you know all the operations it does are performed before any other UI event handlers are called. You lose that atomicity with async code and enter the world of "async hell" where your event handlers are running concurrently to each other.

Kotlin makes this fact explicit, and that's great: as long as your code path doesn't enter a coroutine builder, you know you have the guarantee of atomicity. You must always choose wisely where to lose it because once you do, the complexity of all the rest of your program rises.

When you write

override fun onSomething() {
   val v0 = getV0()
   launch(UI) { 
        val v1 = getV1Async()
        val v2 = v1.await()
        useV2ToUpdateTheGUI(v2)
   }
   val v4 = getV4()
}

this will be the order of execution of your handler code:

  1. v0 = getV0()
  2. v4 = getV4()
  3. onSomething handler returns
  4. some other handlers run
  5. v1 = getV1Async()
  6. some other handlers run
  7. v2 = v1.await()
  8. useV2ToUpdateTheGUI(v2)

An uncontrolled amount of unknown code will run after your onSomething handler returns at 3. above. Most notoriously, your own handlers run, and none are allowed to assume that all the operations initiated in onSomething are done. Whenever you want to use the value of v2, you must add code to decide what to do if it's still not ready.

You can hide the launch call behind a fun that onSomething calls, but then you'll have to carefully explain in the comments/docs that this function just fires off a concurrent task. Naturally, you won't be able to use the result of that task in the body of the handler.

My experience is that you should either have the launch(UI) explicitly in the handler or you should name your method launchFooBar().