83

I want to create a coroutine method which has returning value.

For example)

fun funA() = async(CommonPool) {
    return 1
}

fun funB() = async(CommonPool) {
    return 2
}

fun sum() {
    launch {
        val total = funA().await() + funB().await()
    }
}

If I want to return total in sum method, how should I do?

like,

fun sum(): Int {
    launch {
        val total = funA().await() + funB().await()
    }   

    return total
}
5
  • 8
    Just as a sidenote: When you do funA().await() + funB().await() you are actually running funA() and funB() sequentially. If you want to run them in parallel, you should do val a = funA(), and val b = funB(), and then do a.await() + b.await(). Commented Nov 1, 2017 at 15:06
  • 1
    This is really good suggestions. I am new to kotlin, may I know how it makes difference when we use a.await() rather than funA().await(). How come using variable both functions start processing in parallel. Commented Mar 13, 2020 at 1:01
  • 1
    @Sharad This is because await yields execution (temporarily stops the function) until the promise resolves. If you call two coroutines first, both with start before you reach await. The value of these two variables will be an unresolved promise, which is why you need the await. Commented Jul 11, 2020 at 10:28
  • Does this answer your question? Returning a value produced in Kotlin coroutine Commented Aug 3, 2020 at 21:09
  • 1
    Can you explain why you want sum to be a non-suspending function? The callers of sum should call it inside a coroutine instead of turning asynchronous code into synchronous, which defeats the purpose of coroutines. You would have to block the caller of fun sum. If your caller is synchronous, then I would think this is the problem, and your question is searching for a band aid for the symptom, not the cause. Commented Feb 2, 2021 at 10:29

8 Answers 8

77

To return exactly Int, you need to get out of the coroutine world and that's what runBlocking is for:

fun sum(): Int = runBlocking {
    funA().await() + funB().await()
}

See Bridging blocking and non-blocking worlds in the coroutine guide, and Composing suspending functions for how to do it if you want to use sum inside coroutines.

Sign up to request clarification or add additional context in comments.

4 Comments

is there a way to do the same without Blocking the main thread?
@Angelina 1. This blocks the thread calling this function, which may or may not be the main thread. 2. The only way to do this without blocking the calling thread is making the fun suspend. See the second link for more.
Can't you just add a parameter such as runBlocking(Dispatchers.IO)? It seems to run. But I admit that I haven't exhaustively tested it. Should there be problems?
@SMBiggs The issue is the same: by definition it "blocks the current thread until its completion".
23

If I understand your question correctly, you want to create a function ex. "doWork()" that does work on a coroutine and returns the result of that work directly and concisely being able to update your UI without freezing it.

Thus, based on the above:

First, I would totally avoid using runBlocking , as it will block the UI thread or main thread eventually till 'doWork()' finishes its work on whatever other thread you specify.

I find the most appropriate solution to achieve your goal is by using Koltin Flow as you will be able to publish the progress later on if you want to in the future.

Here's how:

fun doWork(): Flow<Int> =
        flow {
            //do long work 
           val sum:Int = doCalculations()
           emit(sum)
        }.flowOn(Dispatchers.Default)

The flow will run on a coroutine by default.

And in your view you call this function:

launch(Dispatchers.Main){
    doWork().single{
    val my result=it
    //Update your UI
    }
}

Another simpler solution:

Make doWork() a suspend function

   suspend fun doWork(): Int =
          withContext(Dispatchers.Default){
                //do long work 
               val sum:Int = doCalculations()  
               return@withContext sum
}

And in your view you call this function:

launch(Dispatchers.Main){
    val my result=doWork()
    //Update your UI
    }
}

Comments

17

It may be late to answer this question but hopefully someone will find it useful. The code snippet below calculates sum of 3 values A + B + C. Each value is calculated independently in its own background thread in parallel and then all interim results consolidated into one final result and returned to the main thread to display it on a screen.

So it takes 5 seconds to calculate the final value (not 10 seconds = 2 + 3 + 5) and the result is 6 obviously and it's non-blocking, the main thread can handle other events while sum() execution is not complete.

suspend fun sum(scheduler: ThreadPoolExecutor): Int = coroutineScope {

    withContext(scheduler.asCoroutineDispatcher()) {
        val a = async { funA() }
        val b = async { funB() }
        val c = async { funC() }

        a.await() + b.await() + c.await()
    }
}

fun funA(): Int {
    Thread.sleep(2000L)
    return 1
}

fun funB(): Int {
    Thread.sleep(3000L)
    return 2
}

fun funC(): Int {
    Thread.sleep(5000L)
    return 3
}

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val tripletsPool = ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS, LinkedBlockingQueue())

   ...

    override fun onClick(view: View?) {
        if (view == null) {
            return
        }

        when (view.id) {
            R.id.calculate -> {
                GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT) {
                    progressBar.visibility = View.VISIBLE
                    result.setText("${sum(tripletsPool)}")
                    progressBar.visibility = View.GONE
                }
            }
        }
    }
}

Comments

11

Adding another way of achieving it.

fun sum(): Int {
    var sum: Int = 0
    runBlocking {
        val jobA = async { funA() }
        val jobB = async { funB() }
        runBlocking{
           sum = jobA.await() + jobB.await()
        }
    }
    return sum
}

suspend fun funA(): Int {
    return 1
}

suspend fun funB(): Int {
    return 2
}

1 Comment

Can you explain why two runBlocking blocks?
11

Here's another way to run funA() and funB() in parallel without using runBlocking.

fun funA() = CoroutineScope(Dispatchers.Default).async {
    delay(3000)
    return@async 1
}

fun funB() = CoroutineScope(Dispatchers.Default).async {
    delay(3000)
    return@async 2
}

fun sum() = CoroutineScope(Dispatchers.Default).async {
    val a = funA()
    val b = funB()
    return@async a.await() + b.await()
}

And if you want to run sum() without blocking the main thread,

CoroutineScope(Dispatchers.Default).launch { // Use Dispatchers.IO for database or file I/O
    measureTimeMillis {
        Log.d("TAG", "sum=${sum().await()}")
    }.also {
        Log.d("TAG", "Completed in $it ms")
    }
}

Comments

7

I edit your work, i change the funA and funB into a suspend function and i created a function for sum operator and i call on main function, this the example :

suspend fun funA(): Int{
    return 1
}

suspend fun funB(): Int {
    return 2
}
fun sum() = runBlocking{
    val resultSum = async { funA.await() + funB.await() }
    return resultSum
}

fun main() = runBlocking{
    val result = async { sum() }
    println("Your result: ${result.await()}")
}

Hope it will help

2 Comments

Could you add the type sum() is returning and the type of resultSum? That is the part that is confusing me. It looks like sum() is already launching a coroutine and returning it as a promise. So why is main() launching another coroutine just to call sum()? Could't it just call sum() without putting it inside an async (since it's returning an async) and then still call result.await()?
How come .await() is not available on my suspend function?
2

Here is the way I did it, to return a Boolean value when trying to delete a phone number from my Room database. You can use the same pattern for what you are trying to accomplish. In my view model:

private var parentJob = Job()
private val coroutineContext: CoroutineContext get() = parentJob + Dispatchers.Main
private val scope = CoroutineScope(coroutineContext)

suspend fun removePhoneNumber(emailSets: EmailSets, personDetails: PersonDetails) : Boolean  {
    var successReturn = false
    scope.async(Dispatchers.IO) {
        val success = async {removePhoneNumbersAsync(emailSets,personDetails)}
        successReturn = success.await()

    }
    return successReturn
}

fun removePhoneNumbersAsync(emailSets: EmailSets, personDetails : PersonDetails):Boolean {
    var success = false
    try {
        val emailAddressContact = EmailAddressContact(emailSets.databaseId, personDetails.id, personDetails.active)
        repository.deleteEmailAddressContact(emailAddressContact)
        val contact = Contact(personDetails.id, personDetails.personName, personDetails.personPhoneNumber, 0)  
        repository.deleteContact(contact)
        success = true
    } catch (exception: Exception) {
        Timber.e(exception)
    }
    return success
}

In my Activity:

runBlocking {
    if (v.tag != null) {
            val personDetails = v.tag as PersonDetails
            val success  = viewModel.removePhoneNumber(emailSets,personDetails)
            if (success) {
                val parentView = v.parent as View
                (parentView as? TableRow)?.visibility = View.GONE
                val parentViewTable = parentView.parent as ViewGroup
                (parentViewTable as? TableLayout)
                parentViewTable.removeView(parentView)
            }
     }

}

Comments

0

If A() happens to return some value and that return value is handle by suspend fun B() you can simply do this:

suspend fun A() {
        return coroutineScope {
            
            return@coroutineScope B() // here your returning B() 
        }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.