在后台线程中执行硬任务,在主线程中返回结果

时间:2019-03-27 15:25:19

标签: android multithreading kotlin android-asynctask

我花了一些时间来找到开发人员友好的解决方案(不向项目中添加依赖项),该解决方案涉及如何在后台线程中执行一些艰巨的任务,并在任务完成后将结果返回到主线程。我发现“ AsyncTask”可以做到这一点。但是要使用它,您需要为需要在后台运行的每个任务编写样板代码。我是iOS开发人员,决定尝试与Android相关的开发。因此,在Swift中,您只需使用下面的代码即可完成此任务:

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

这看起来很简单。但是在Kotlin中,我找不到这种简单的解决方案,因此决定创建它。

这就是我做的:

我创建了通用类

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)
    }
}

和经理

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
        }
    }

}

现在我这样使用它:

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

看起来对开发人员友好。

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")

和日志:

  

2019-03-27 17:11:00.719 17082-1​​7082 / com.test.testapp E /主要:主要   不应阻塞螺纹

     

2019-03-27 17:11:00.722 17082-1​​7082 / com.test.testapp E /主要:主要   不应阻止线程-1

     

2019-03-27 17:11:00.722 17082-1​​7124 / com.test.testapp E / TASK:已启动   后台任务

     

2019-03-27 17:11:05.737 17082-1​​7124 / com.test.testapp E /任务:已完成   具有结果的后台任务:后台的值

     

2019-03-27 17:11:05.738 17082-1​​7082 / com.test.testapp E /任务:已启动   主线程中的任务,其背景为背景:来自的值   背景

所以问题是专业的Android开发人员对该解决方案有何看法。万一我要使用它,我会遇到什么问题。也许有某些原因不使用此解决方案。

2 个答案:

答案 0 :(得分:2)

ianhanniballake's answer是正确的,但可能有点不完整,因此我想提供一个完整的通用示例。

build.gradle(:app)

dependencies { // this line is probably already present
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
}

全局CoroutineScope不绑定任何作业。
使用GlobalScope 推出整体运作的顶级协程 应用程序寿命,并且不会过早取消。应用 代码通常应使用应用程序定义的CoroutineScope。
使用 强烈建议不要在GlobalScope实例上进行异步或启动。 取自here

因此,您想将具有生命周期的任何类用作CoroutineScope,以便其死亡时将正在运行的后台任务带到坟墓。人们经常建议为此使用一项活动。但是,有一个case to be made,您不希望任何外部类将您的活动用作其CoroutineScope,因此您可以改用受保护的字段:

protected val scope = CoroutineScope(Job() + Dispatchers.Main)

在撰写本文时,我不知道为什么必须在这里创建Job()。我所知道的是+运算符已重载以将这两个上下文合并为一个。对于分派器部分,您可以选择一个合理的部分。选项包括

  • Dispatchers.Main用于UI线程
  • Dispatchers.Default用于后台线程池
  • Dispatchers.IO用于阻止I / O密集型操作
  • Dispatchers.Unconfined表示何时真正知道自己在做什么。 This article不鼓励“正常”使用它。

现在,所有这一切都变得毫无用处了,代码变得异常简单:

import kotlin.coroutines.*
// ...
myButton.setOnClickListener() { v: View? ->
                myButton.setColorToYellow() // some UI thread work
                scope.launch(Dispatchers.Default) {
                    val result = longComputation() // some background work

                    withContext(Dispatchers.Main) {
                        // some UI thread work for when the background work is done
                        root.findViewById<TextView>(R.id.text_home).text = "Result: $result"
                    }
                }
                myButton.setColorToRed() // more UI thread work. this is done instantly
       }

当然,这可以在任何地方完成-我只是使用一个按钮和一个onClickListener来举例说明可能的用例。

答案 1 :(得分:1)

如果您使用的是Kotlin,则正确的方法是通过Coroutines,它可以使您编写如下代码:

// 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)
}

请注意,Kotlin协程在structured concurrency下运行,因此您通常希望避免使用GlobalScope,而是将协程的范围限定为与“活动/片段”生命周期相关联。通常,此刻需要立即自己完成。