Search code examples
androidmultithreadingkotlinandroid-asynctask

Execute hard task in Background Thread, return result in Main Thread


I spent some time to find a developer friendly solution (without adding dependencies to the project) of how to perform some hard task in background thread and after the task is completed return result to main thread. I found "AsyncTask" which allows to do that. But to use it you need to write boilerplate code for each task you need to run in Background. I am iOS developer who decided to try Android-related developing. So in Swift you can simply use the next code to make this task:

DispatchQueue.global().async(execute: {
      //Do some hard task in background
   DispatchQueue.main.async(execute: {
      //Return to main
   })
})

This looks pretty simple. But in Kotlin I didn't find such simple solution and decided to create it.

Here is what I made:

I created Generic class

import android.os.AsyncTask

class BaseAsyncTask<M>: AsyncTask<()->M, Int, M>() {

    var completion: ((M)->Unit)? = null

    override fun doInBackground(vararg params: (() -> M)?): M? {
        for (p in params) {
            return p?.invoke()
        }
        return  null
    }

    override fun onPostExecute(result: M) {
        super.onPostExecute(result)

        completion?.invoke(result)
    }
}

And Manager

class AsyncManager {

    companion object {

        fun <M>execute(inBackground: ()->M, inMain: (M)->Unit): BaseAsyncTask<M> {
            val task = BaseAsyncTask<M>()
            task.completion = inMain
            task.execute(inBackground)

            return task
        }

        fun <M>execute(inBackground: ()->M): BaseAsyncTask<M> {
            val task = BaseAsyncTask<M>()
            task.execute(inBackground)

            return task
        }
    }

}

Now I use it like this:

AsyncManager.execute({
   //Do some hard task in background
}, {
  //Return to main
})

Looks developer friendly.

Log.e("MAIN", "MAIN THREAD SHOULD NOT BE BLOCKED")

AsyncManager.execute({
    Log.e("TASK", "Started background task")
    val retval = "The value from background"
    Thread.sleep(5000)
    Log.e("TASK", "Finished background task with result: " + retval)
    retval
}, {
    Log.e("TASK", "Started task in Main thread with result from Background: " + it)
})

Log.e("MAIN", "MAIN THREAD SHOULD NOT BE BLOCKED - 1")

And the log:

2019-03-27 17:11:00.719 17082-17082/com.test.testapp E/MAIN: MAIN THREAD SHOULD NOT BE BLOCKED

2019-03-27 17:11:00.722 17082-17082/com.test.testapp E/MAIN: MAIN THREAD SHOULD NOT BE BLOCKED - 1

2019-03-27 17:11:00.722 17082-17124/com.test.testapp E/TASK: Started background task

2019-03-27 17:11:05.737 17082-17124/com.test.testapp E/TASK: Finished background task with result: The value from background

2019-03-27 17:11:05.738 17082-17082/com.test.testapp E/TASK: Started task in Main thread with result from Background: The value from background

So the question is what professional Android developers think about this solution. What problem can I get in case I'll use it. And maybe there is some reason not to use this solution.


Solution

  • If you're using Kotlin, the correct way to do this is via Coroutines, which would allow you to write code such as:

    // Launch a coroutine that by default goes to the main thread
    GlobalScope.launch(Dispatchers.Main) {
        // Switch to a background (IO) thread
        val retval = withContext(Dispatchers.IO) {
            Log.e("TASK", "Started background task")
            val retval = "The value from background"
            Thread.sleep(5000)
            Log.e("TASK", "Finished background task with result: " + retval)
            retval
        }
        // Now you're back the main thread
        Log.e("TASK", "Started task in Main thread with result from Background: " + retval)
    }
    

    Note that Kotlin coroutines operate under structured concurrency, so you'd generally want to avoid using GlobalScope and instead scope your coroutine to be tied to your Activity / Fragment lifecycle. This generally needs to be done yourself right now.